README.md
When publishing a package to the public npm
registry,
use a “Version Badge” from badge.fury.io to display the version and link to the package page on npmjs.com.
latest version published to npm
instead the default npm version
alt wording.badge.fury.io
server.Example Markdown snippet (replace helloworld
placeholder):
# Helloworld
[![latest version published to npm](https://badge.fury.io/js/helloworld.svg)](https://www.npmjs.com/package/helloworld)
`helloworld` is a JavaScript package that is published on npm and it does useful things like...
When testing on Travis CI, use Travis CI’s native badge.
Example Markdown snippet (replace chbrown/helloworld
placeholder):
[![Travis CI build status](https://travis-ci.org/chbrown/helloworld.svg?branch=master)](https://travis-ci.org/chbrown/helloworld)
When tracking code coverage on Coveralls, use Coveralls’s native badge.
Example Markdown snippet (replace chbrown/helloworld
placeholder):
[![Coverage status on Coveralls](https://coveralls.io/repos/github/chbrown/helloworld/badge.svg?branch=master)](https://coveralls.io/github/chbrown/helloworld?branch=master)
Use mocha, nyc, and coveralls.
npm i -D mocha @types/mocha nyc coveralls
Why mocha
?
tap
is far less popularjest
is too Facebook/React-orientedava
… maybe preferable? (It comes with TypeScript definitions!)E.g.:
{
...,
"devDependencies": {
"@types/mocha": "^2.2.48",
"@types/node": "latest",
"coveralls": "^3.0.0",
"mocha": "^5.0.4",
"nyc": "^11.4.1",
"typescript": "^2.7.2"
},
...
}
Your tests should be in ./test/*.js
.
By default, mocha
looks for tests in the test
directory, so keeping to that is easiest.
On your local development machine, ~/.nycrc
should look like:
{
"temp-directory": "./._nyc_output"
}
nyc
’s default temp-directory
is ./.nyc_output
.
However, npm ignores ._*
by default, so we piggyback on that functionality.
This is only on the local machine – it doesn’t matter if Travis CI clutters up the local directory, because it’s not publishing anything to npm.
package.json
should have these scripts
entries:
{
...,
"scripts": {
"pretest": "tsc",
"test": "nyc mocha",
"posttest": "nyc report --reporter=text-lcov | coveralls || true",
...
}
}
The posttest
stage doesn’t run if test
fails, and so the || true
isn’t going to swallow any breakage,
and it protects against false-negative “failure” if coveralls isn’t behaving/properly configured.
It looks like there’s no way to script the “ADD REPO” action on the Coverall.io website.
To do this manually, go to https://coveralls.io/repos/new, find the repo,
and click the [__|OFF]
toggle so that it becomes [ON|___]
.
http://registry.npmjs.org/
is a CouchDB endpoint, but doesn’t support /_utils/
navigation.
http://registry.npmjs.org/
{
db_name: "registry",
doc_count: 116761,
doc_del_count: 378,
update_seq: 305896,
purge_seq: 0,
compact_running: false,
disk_size: 672571515,
data_size: 420639278,
instance_start_time: "1413839504850820",
disk_format_version: 6,
committed_update_seq: 305896
}
http://registry.npmjs.org/-/all
This pulls down a huge ~85 MB one-line JSON file. The keys are the package names, and they are alphabetically sorted. It’s pretty speedy, though — I get 30+ MB/s when calling it from a Linode.
{
"_updated": 1417985649051,
...
"amulet": {
"name": "amulet",
"description": "As-soon-as-possible streaming async mustache templating",
"dist-tags": {
"latest": "1.0.7"
},
"maintainers": [
{
"name": "chbrown",
"email": "[email protected]"
}
],
"author": {
"name": "Christopher Brown",
"email": "[email protected]",
"url": "http://henrian.com"
},
"repository": {
"type": "git",
"url": "git://github.com/chbrown/amulet.git"
},
"time": {
"modified": "2014-01-14T03:24:23.968Z"
},
"versions": {
"1.0.7": "latest"
},
"keywords": [
"template",
"mustache",
"mu",
"asynchronous",
"streaming",
"nodeps"
]
},
...
"flickr-with-uploads": {
"name": "flickr-with-uploads",
"description": "Flickr API with OAuth 1.0A and uploads",
"dist-tags": {
"latest": "2.0.4"
},
"maintainers": [
{
"name": "chbrown",
"email": "[email protected]"
}
],
"repository": {
"type": "git",
"url": "git://github.com/chbrown/flickr-with-uploads.git"
},
"author": {
"name": "Christopher Brown",
"email": "[email protected]",
"url": "http://henrian.com"
},
"homepage": "https://github.com/chbrown/flickr-with-uploads",
"keywords": [
"flickr",
"api",
"oauth",
"upload",
"backup",
"archive",
"sync"
],
"contributors": [
{
"name": "Sujal Shah",
"email": "[email protected]"
},
{
"name": "Thomas",
"url": "https://github.com/tomap"
},
{
"name": "Tomasz Kołodziejski",
"url": "https://github.com/neojski"
}
],
"bugs": {
"url": "https://github.com/chbrown/flickr-with-uploads/issues",
"email": "Christopher Brown <[email protected]>"
},
"license": "MIT",
"readmeFilename": "README.md",
"time": {
"modified": "2014-05-02T17:46:51.354Z"
},
"versions": {
"2.0.4": "latest"
}
},
...
}
You can use the /since
path to get updates, instead of the full file.
(See lib/cache/update-index.js in the npm
source code for usage.)
For example:
https://registry.npmjs.org/-/all/since?stale=update_after&startkey=1431729476513
This will respond with an HTTP 302 redirect, depending on how long ago startkey
is.
startkey
is just the date you want to get updates after,
represented as the number of milliseconds since the epoch,
i.e., what new Date().getTime()
returns.
Here are some example Location
’s it responds with:
https://registry.npmjs.org/-/all/static/yesterday.json
https://registry.npmjs.org/-/all/static/fortnight.json
https://registry.npmjs.org/-/all/static/quarter.json
https://registry.npmjs.org/-/all/static/month.json
https://registry.npmjs.org/-/all/static/year.json
https://registry.npmjs.org/-/all/static/all.json
(for startkey=0
)Or it may respond with a server failure; apparently it doesn’t just resolve a time difference to a specific name.
http://registry.npmjs.org/-/by-user/chbrown|isaacs
{
"chbrown": [
"adts", "amulet", "autoauth", "birdy", "brew-tour", "cameo", "cameo-crawler",
"cameo-twitter", "domlike", "dropyll", "fapply", "flickr-sync", "flickr-with-uploads",
"fs-change", "gzbz", "http-enhanced", "iwc", "jsed", "json-cmd", "less-watch",
"lexicons", "loge", "node_restarter", "npm-search-downloads", "osx-notifier",
"parallel-wget", "ranges", "regex-router", "rfc6902", "ruthless", "sqlcmd",
"streaming", "sv", "tex", "turk", "twilight", "visible", "winston-notification-center",
"xmlconv", "yaml2sql"
],
"isaacs": [
"abbrev", "asshole", "async-cache", "bench", "bitch", "block-stream",
"brace-expansion", "callback-tracker", "canonical-host", "char-spinner",
"chmodr", "chownr", "cluster-callresp", "cluster-master", "cocksucker",
"coffee-cleanse", "coffeescript", "config-chain", "core-util-is", "couch-login",
"couch-readonly-replica", "couchdb-log-parse", "csrf-lite", "cunt", "cuttlefish",
"dezalgo", "domain-http-server", "duplex-passthrough", "eliza", "emcee",
"error-page", "fag", "faggot", "fast-list", "fastly", "filewatcherthing",
"fs-write-stream-atomic", "fstream", "fstream-ignore", "fstream-npm", "ghlink",
"gist-cli", "github-flavored-markdown", "glob", "graceful-fs", "gyp-reader",
"hardhttps", "hexedit", "http-agent", "http-https", "inflight", "inherits",
"ini", "init-package-json", "irssi-questions", "json-stringify-safe", "lockfile",
"lru-cache", "lylog", "manta-client", "mcouch", "minimatch", "multi-fs",
"mute-stream", "nave", "nigger", "node", "node-date", "node-gyp", "node-strict",
"noexit", "nopt", "normalize-package-data", "nosync", "npm", "npm-cache-dir",
"npm-cache-filename", "npm-docsite", "npm-expansions", "npm-fullfat-registry",
"npm-install-checks", "npm-intro-slides", "npm-package-arg", "npm-registry-client",
"npm-registry-couchapp", "npm-registry-readme-trim", "npm-skim-registry",
"npm-user-validate", "npm-www", "npmconf", "npmjs.org", "npmlog", "once",
"osenv", "parse-json-response", "penis", "penis.js", "pingme", "promzard",
"proto-list", "read", "read-installed", "read-package-json", "read-package-tree",
"readable-stream", "readdir-scoped-modules", "redsess", "rimraf", "sax",
"semicolons", "semver", "seq-file", "server-destroy", "sigmund", "simple-protocol",
"slide", "sodn", "srand", "ssh-key-decrypt", "st", "stream-multiplexer",
"streams2", "tako-cookies", "tako-session-token", "tap", "tap-assert",
"tap-consumer", "tap-global-harness", "tap-harness", "tap-producer",
"tap-results", "tap-runner", "tap-test", "tar", "tcp", "templar",
"tesla.svc.core.routes", "touch", "truncating-stream", "tt", "udp",
"uid-number", "underscorify", "use-strict", "util-extend", "voxer-blog-demo",
"which", "wrappy", "yamlish"
]
}
http://registry.npmjs.org/-/user/org.couchdb.user:chbrown
{
"_id": "org.couchdb.user:chbrown",
"_rev": "5-8622978234df9353110443726e862552",
"fullname": "Christopher Brown",
"fields": [
{
"name": "fullname",
"value": "Christopher Brown",
"title": "Full Name",
"show": "Christopher Brown"
},
{
"name": "email",
"value": "[email protected]",
"title": "Email",
"show": "<a href=\"mailto:[email protected]\">[email protected]</a>"
},
{
"name": "github",
"value": "https://github.com/chbrown",
"title": "Github",
"show": "<a rel=\"me\" href=\"https://github.com/https://github.com/chbrown\">https://github.com/chbrown</a>"
},
{
"name": "twitter",
"value": "https://twitter.com/chbrown",
"title": "Twitter",
"show": "<a rel=\"me\" href=\"https://twitter.com/https://twitter.com/chbrown\">@https://twitter.com/chbrown</a>"
},
{
"name": "appdotnet",
"value": "",
"title": "App.net",
"show": ""
},
{
"name": "homepage",
"value": "http://henrian.com/",
"title": "Homepage",
"show": "<a rel=\"me\" href=\"http://henrian.com/\">http://henrian.com/</a>"
},
{
"name": "freenode",
"value": "",
"title": "IRC Handle",
"show": ""
}
],
"avatarMedium": "https://secure.gravatar.com/avatar/56255b31d278b0c193f0f299ab01561b?s=100&d=retro",
"name": "chbrown",
"type": "user",
"github": "chbrown",
"homepage": "http://henrian.com/",
"email": "[email protected]",
"twitter": "chbrown",
"date": "2013-05-06T04:33:50.569Z",
"avatar": "https://secure.gravatar.com/avatar/56255b31d278b0c193f0f299ab01561b?s=50&d=retro",
"freenode": "",
"avatarLarge": "https://secure.gravatar.com/avatar/56255b31d278b0c193f0f299ab01561b?s=496&d=retro",
"appdotnet": "",
"roles": []
}
http://registry.npmjs.org/amulet
{
"_attachments": {},
"_id": "amulet",
"_rev": "46-8ea15f9850c9574de038d4f9cb0908b8",
"author": {
"email": "[email protected]",
"name": "Christopher Brown",
"url": "http://henrian.com"
},
"description": "As-soon-as-possible streaming async mustache templating",
"dist-tags": {
"latest": "1.0.7"
},
"maintainers": [
{
"email": "[email protected]",
"name": "chbrown"
}
],
"name": "amulet",
"readme": "# Amulet - Mustache templating for Node.js\n\nMustache is a ... ## License\n\nCopyright © 2011–2013 Christopher Brown. [MIT Licensed](LICENSE).\n",
"repository": {
"type": "git",
"url": "git://github.com/chbrown/amulet.git"
},
"time": {
"0.1.5": "2011-08-24T20:49:39.725Z",
...
"1.0.7": "2014-01-14T03:24:23.969Z",
"created": "2011-08-24T20:49:38.532Z",
"modified": "2014-01-14T03:24:23.968Z"
},
"versions": {
...
"1.0.7": {
"_from": ".",
"_id": "[email protected]",
"_npmUser": {
"email": "[email protected]",
"name": "chbrown"
},
"_npmVersion": "1.3.21",
"author": {
"email": "[email protected]",
"name": "Christopher Brown",
"url": "http://henrian.com"
},
"bugs": {
"email": "Christopher Brown <[email protected]>",
"url": "https://github.com/chbrown/amulet/issues"
},
"description": "As-soon-as-possible streaming async mustache templating",
"devDependencies": {
"portfinder": "*",
"request": "*",
"tap": "*"
},
"directories": {},
"dist": {
"shasum": "34f77074a0517fb21876f58bdf447c4e8cc3c56c",
"tarball": "http://registry.npmjs.org/amulet/-/amulet-1.0.7.tgz"
},
"homepage": "https://github.com/chbrown/amulet",
"keywords": [
"template",
"mustache",
"mu",
"asynchronous",
"streaming",
"nodeps"
],
"license": "MIT",
"maintainers": [
{
"email": "[email protected]",
"name": "chbrown"
}
],
"name": "amulet",
"readme": "# Amulet - Mustache templating for Node.js\n\nMustache is a simple, ... Christopher Brown. [MIT Licensed](LICENSE).\n",
"readmeFilename": "README.md",
"repository": {
"type": "git",
"url": "git://github.com/chbrown/amulet.git"
},
"scripts": {
"test": "tap test"
},
"version": "1.0.7"
}
}
}
You can also use a semver
specifier in the version position. The following all return the same payload.
http://registry.npmjs.org/amulet/*
http://registry.npmjs.org/amulet/~1.0
http://registry.npmjs.org/amulet/1.0.7
{
"_from": ".",
"_id": "[email protected]",
"_npmUser": {
"email": "[email protected]",
"name": "chbrown"
},
"_npmVersion": "1.3.21",
"author": {
"email": "[email protected]",
"name": "Christopher Brown",
"url": "http://henrian.com"
},
"bugs": {
"email": "Christopher Brown <[email protected]>",
"url": "https://github.com/chbrown/amulet/issues"
},
"description": "As-soon-as-possible streaming async mustache templating",
"devDependencies": {
"portfinder": "*",
"request": "*",
"tap": "*"
},
"directories": {},
"dist": {
"shasum": "34f77074a0517fb21876f58bdf447c4e8cc3c56c",
"tarball": "http://registry.npmjs.org/amulet/-/amulet-1.0.7.tgz"
},
"homepage": "https://github.com/chbrown/amulet",
"keywords": [
"template",
"mustache",
"mu",
"asynchronous",
"streaming",
"nodeps"
],
"license": "MIT",
"maintainers": [
{
"email": "[email protected]",
"name": "chbrown"
}
],
"name": "amulet",
"readme": "# Amulet - Mustache templating for Node.js\n\nMustache is a simple ... Christopher Brown. [MIT Licensed](LICENSE).\n",
"readmeFilename": "README.md",
"repository": {
"type": "git",
"url": "git://github.com/chbrown/amulet.git"
},
"scripts": {
"test": "tap test"
},
"version": "1.0.7"
}
Download counts are relegated to a separate API, at the domain api.npmjs.org
.
The documentation at GitHub: npm/download-counts is almost comprehensive.
The api.npmjs.org URLs point to a Joyent Manta server, proxied through a Fastly CDN, and the SSL is broken.
All insecure http:
requests are redirected to https:
, so the easiest solution is not to verify the SSL certificate.
The point urls return flat JSON responses.
https://api.npmjs.org/downloads/point/last-month/flickr-with-uploads
{
"downloads": 113,
"start": "2014-11-08",
"end": "2014-12-07",
"package": "flickr-with-uploads"
}
https://api.npmjs.org/downloads/point/last-week/amulet
{
"downloads": 44,
"start": "2014-12-01",
"end": "2014-12-07",
"package": "amulet"
}
https://api.npmjs.org/downloads/point/last-week/amulet
{
"downloads": 44,
"start": "2014-12-01",
"end": "2014-12-07",
"package": "amulet"
}
https://api.npmjs.org/downloads/point/last-day/request
{
"downloads": 87463,
"start": "2014-12-07",
"end": "2014-12-07",
"package": "request"
}
You can also ask for a range, which returns much the same thing, only unsummed. If a day has 0 downloads, it will be missing from the result.
https://api.npmjs.org/downloads/range/last-week/http-enhanced
{
"downloads": [
{"day": "2014-12-02", "downloads": 10 },
{"day": "2014-12-03", "downloads": 71 },
{"day": "2014-12-04", "downloads": 1 },
{"day": "2014-12-06", "downloads": 1 }
],
"start": "2014-12-01",
"end": "2014-12-07",
"package": "http-enhanced"
}
You can call both point/range download requests with no package name to get overall counts.
https://api.npmjs.org/downloads/range/last-month
{
"downloads": [
{"day": "2014-11-08", "downloads": 10541316 },
{"day": "2014-11-09", "downloads": 9654281 },
{"day": "2014-11-10", "downloads": 24328909 },
{"day": "2014-11-11", "downloads": 25790529 },
{"day": "2014-11-12", "downloads": 26228722 },
{"day": "2014-11-13", "downloads": 26855130 },
{"day": "2014-11-14", "downloads": 25754384 },
{"day": "2014-11-15", "downloads": 12493827 },
{"day": "2014-11-16", "downloads": 11042083 },
{"day": "2014-11-17", "downloads": 26490237 },
{"day": "2014-11-18", "downloads": 28054740 },
{"day": "2014-11-19", "downloads": 28359947 },
{"day": "2014-11-20", "downloads": 28174239 },
{"day": "2014-11-21", "downloads": 25498948 },
{"day": "2014-11-22", "downloads": 12266345 },
{"day": "2014-11-23", "downloads": 10903174 },
{"day": "2014-11-24", "downloads": 25178567 },
{"day": "2014-11-25", "downloads": 28837632 },
{"day": "2014-11-26", "downloads": 26392288 },
{"day": "2014-11-27", "downloads": 20609579 },
{"day": "2014-11-28", "downloads": 18262603 },
{"day": "2014-11-29", "downloads": 9776744 },
{"day": "2014-11-30", "downloads": 10335674 },
{"day": "2014-12-01", "downloads": 25455841 },
{"day": "2014-12-02", "downloads": 20497259 },
{"day": "2014-12-03", "downloads": 27602909 },
{"day": "2014-12-04", "downloads": 20523249 },
{"day": "2014-12-05", "downloads": 26726277 },
{"day": "2014-12-06", "downloads": 11870278 },
{"day": "2014-12-07", "downloads": 10988879 }
],
"start": "2014-11-08",
"end": "2014-12-07"
}
Requesting stats for a missing package will return an HTTP 200 OK, but the response payload will signify an error in different ways for points vs. ranges.
https://api.npmjs.org/downloads/range/last-month/pwjkqolamezt
{
"error": "no stats for this package for this range (0008)"
}
https://api.npmjs.org/downloads/point/last-month/pwjkqolamezt
{
"downloads": null,
"start": "2014-11-08",
"end": "2014-12-07",
"package": null
}