Hermetically sealed versions of node and pnpm.
Instead of relying on the version of node, npm, pnpm, etc. each individual developer or
machine has installed locally, packages use a system we've named HNVM, which stands for Hermetic
Node Version Manager. Having our node binaries be hermetic means each app or package defines what
version of node they depend on. That version is installed and used to run whenever the package
runs scripts in node, npm, pnpm, and yarn. This ensures that everyone's systems output
consistent packages, and upgrades required by one package don't affect another.
Homebrew (Compass Employees Only)
HNVM is distributed via Homebrew inside the
UrbanCompass/versions tap:
brew tap UrbanCompass/versions
# If you get a permissions error tap'ing, try using the repo's ssh url
brew tap UrbanCompass/versions [email protected]:UrbanCompass/homebrew-versions.git
brew install hnvmYou can also use basher to install hnvm:
basher install UrbanCompass/hnvmThis will install both hnvm and jq.
If you can't or don't want to install using homebrew or basher, you can use the provided
install.sh and uninstall.sh scripts to add or remove the local bin path to your global $PATH:
source install.sh
node -v # Uses hnvm node script to get the node version
source uninstall.sh
node -v # No longer uses hnvm node scriptNote that hnvm depends on jq being available in your global
$PATH. If you don't install hnvm via brew or basher, you'll have to make sure that jq is installed on your own.
HNVM reads the version of node/pnpm/yarn set in your package.json file' "engines" field. If
no version is set, it will default to the current versions set in HNVM's own package.json. Unlike
HNVM 1.0, you don't have to find any particular bash script to run HNVM. Just use the regular
node, npm, etc. commands you're used to from anywhere on your computer. If you run it
next to a package.json file, it will read the engines field. If not, it'll default to the global
version.
// main.js
console.log('Node version', process.version);# package.json in this directory has the hnvm version set to 12.10.0
node main.js # Echo's: "Node version v12.10.0HNVM reads its configuration from one of the following places, in order from highest to lowest priority:
- The
package.jsonfile in the current process working directory ($PWD/package.json) - An
.hnvmrcfile in thePWD($PWD/.hnvmrc) - An
.hnvmrcfile at the root of your git repo (if running in a git repo) - An
.hnvmrcfile in your home directory (~/.hnvmrc) - The
.hnvmrcfile that ships with the current version of HNVM
The .hnvmrc files are all simple key/value pairs prepended with HNVM_:
HNVM_PATH=/path/to/.hnvm
HNVM_NODE=10.0.0
HNVM_PNPM=3.0.0
HNVM_YARN=1.19.0The full list of config options are detailed below.
Versions configured in package.json files go in either the "engines" field or an
"hnvm" field, with the latter overriding the former:
{
"name": "some-package",
"version": "1.0.0",
"engines": {
"node": "10.0.0",
"pnpm": "3.0.0",
"yarn": "1.19.0"
},
"hnvm": {
"node": "11.0.0" // This overrules any versions set in "engines"
}
}The "hnvm" field can contain the same values as in the "engines" field. It's useful for when
you want HNVM to be stricter than your engines. For example, your package might be compatible with a
node version range, but HNVM itself runs at a specific version:
{
"name": "some-package",
"version": "1.0.0",
},
"engines": {
"node": ">=10.0.0"
},
"hnvm": {
"node": "11.0.0"
}
}Location on disk to download binaries to, defaulting to an .hnvm directory in your $HOME
directory.
Version of node/pnpm/yarn to use. If semver ranges are provided instead of exact versions
(e.g. the defaults are set to latest), HNVM will perform curl requests to resolve those to an
exact version. However you'll get a warning about this since it could slow down execution time from
the async request, or it might even fail to work at all if the curl requests fail to load.
It's best to create an .hnvmrc file in your home directory and set the versions to exact versions.
If you want to temporarily override any of these values, you can override the env vars when your run the script:
env HNVM_NODE='12.0.0' node --version # v12.0.0To try to mitigate the slowdown above, semver range results are cached locally. The default is 60s
but you can control this by setting the HNVM_RANGE_CACHE environment variable. To disable the
cache set the value to 0.
HNVM outputs information about the current version of node running, and any download statuses if a
new version is being downloaded so that you know exactly what's going on. If you don't want this
output, set the HNVM_QUIET environment variable to true to have this output redirected to
/dev/null.
Location of the NodeJS distribution files. If you provide a custom destination, you should ensure that the repository layout mirrors that of nodejs.org:
/node-v${node_ver}-${platform}-${cpu_arch}.tar.gz
Variant of the NodeJS distribution files to fetch. This exists mostly to support the musl builds.
When set to true, skips the HEAD request validation before downloading packages. By default, hnvm validates that download URLs are accessible before attempting to download, which provides faster failure feedback and clearer error messages. Set this to true if you want to skip validation (e.g., in environments where HEAD requests are blocked but GET requests work).
If using the --with-pnpm flag, npm-compatible registry to install pnpm from. Defaults to the
public npm registry.
If using the --with-yarn flag, the location from where to download yarn from. When providing a
custom destination, you should ensure the layout mirrors that of yarnpkg.com:
/${yarn_ver}/yarn-v${yarn_ver}.tar.gz
When set to true, HNVM will stop going up the tree to look for .hnvmrc files. This is useful in
monorepo's if you want to enforce that your specific package's HNVM version is run and it doesn't
end up falling back to a version outside of the repo.
# In package's .hnvmrc
HNVM_NODE=11.0.0
# In git root's .hnvmrc
HNVM_NOFALLBACK=true
# In HNVM default .hnvmrc
HNVM_NODE=12.0.0If I run node --version next to the package, I'll get v11.0.0. If I run it outside the package
but still in the repo, I'll get an error asking to specify a node version instead of defaulting to
node v12.0.0.
Why Not Just Use NVM
Because nvm doesn't support fish π
. This project also focuses on individual
projects or packages declaring versions of node to use, as opposed to switching globally between
different versions. It results in a simpler setup with far less code.
NOTE: The tests hit live npm registry servers and go through the process of downloading multiple copies of node. Do not run in bandwidth-constrained environments!
Tests for HNVM are written with jest and require node 14.4.0 or higher (protip: use hnvm to test hnvm π). To run the tests, cd test/ && npm install && npm test