General documentation / cheat sheets for various languages and services

Packaging best practices

README.md

npm

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.

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...

Travis CI

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)

Coveralls

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)

Testing best practices

Preferred setup, assuming TypeScript

Use mocha, nyc, and coveralls.

npm i -D mocha @types/mocha nyc coveralls

Why mocha?

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|___].

registry.npmjs.org

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:

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]henrian.com",
      "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"
}

api.npmjs.org

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
}