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
[](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):
[](https://travis-ci.org/chbrown/helloworld)
When tracking code coverage on Coveralls, use Coveralls’s native badge.
Example Markdown snippet (replace chbrown/helloworld placeholder):
[](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/-/allThis 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.jsonhttps://registry.npmjs.org/-/all/static/fortnight.jsonhttps://registry.npmjs.org/-/all/static/quarter.jsonhttps://registry.npmjs.org/-/all/static/month.jsonhttps://registry.npmjs.org/-/all/static/year.jsonhttps://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.0http://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
}