npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nvmpin

v0.4.0

Published

Pin global npm packages to specific nvm node versions via shims — global CLIs keep working across nvm version switches.

Readme

nvmpin

Keep your global CLIs working across nvm version switches.

nvm silos globally-installed npm packages per node version: run nvm use 20 and every CLI you installed under node 18 vanishes from your PATH. nvmpin fixes this without asking you to leave nvm — it pins each global package to a specific nvm-managed node version and generates tiny bash shims so the CLI is always on your PATH, always running on the node version it was built for.

$ nvm use 20
$ tsc --version        # installed under node 18
zsh: command not found: tsc

$ nvmpin add typescript --node 18
pinned typescript -> node v18.20.4 (bins: tsc, tsserver)

$ nvm use 20 && tsc --version
Version 5.4.5           # still works
  • Zero runtime dependencies. Pure node stdlib.
  • nvm-native. Works with your existing $NVM_DIR layout; never shells out to nvm; changes nothing about how you use nvm.
  • Correct npm. Installs always use the target node version's own node + npm, so native modules compile against the right ABI.

nvmpin vs Volta

Volta solves this problem more completely: it pins node versions per project, hooks package managers, and manages toolchains end to end. If you're happy to replace nvm, use Volta — it's the more mature tool.

nvmpin is for people staying on nvm: your team standardizes on it, your dotfiles assume it, or you just don't want to migrate. nvmpin adds one missing capability — per-package version pinning for global CLIs — on top of the nvm install you already have, and nothing else.

Quickstart

Requires node >= 18, nvm, and a POSIX shell (macOS/Linux, bash/zsh). Windows is not supported.

$ npm install -g nvmpin
$ nvmpin setup

setup creates ~/.nvmpin/bin and prints (and offers to append) the one line you need in your shell rc, after nvm's init lines:

export PATH="$HOME/.nvmpin/bin:$PATH" # nvmpin

Then pin something:

$ nvmpin add typescript --node 18      # installs under v18.x if needed, writes shims
$ nvmpin import                        # or: pin ALL existing globals to where they live now
$ nvmpin list                          # see your pins and their health
$ nvmpin doctor                        # verify PATH order, shims, registry

Command reference

| Command | What it does | | --- | --- | | nvmpin setup | Create dirs and print the shell rc PATH snippet. Offers to append it for bash and zsh; other shells get the snippet with a note to add the equivalent manually. Idempotent. | | nvmpin add <pkg>[@<ver>] --node <v> | Install the package under that node version (if not already there), write shims for every bin it declares, record the pin. If --node is omitted, nvmpin suggests a version from the package's engines.node: manifests already installed under some nvm version are consulted first (offline), and only if the package is installed nowhere is the npm registry asked for the manifest (respects npm_config_registry; 5s timeout). If the lookup fails or yields no usable range, it errors listing your installed versions. Conflicting engines.node produces a warning, not a failure. Re-pinning an already-pinned package to a different version is rejected — use move. | | nvmpin remove <pkg> [--uninstall] | Delete the shims and the registry entry. --uninstall also removes the package from the node version's global tree. | | nvmpin move <pkg> --node <v> | Full reinstall into the new version (never a re-point — native modules are compiled per node ABI), rewrite shims, update the registry. Keeps the currently-installed package version ([email protected]), not npm's latest. Rolls back registry and shims on failure. | | nvmpin move --all [--from <v>] --node <v> | Bulk move: every pin — or, with --from, only pins on that version — to the target. Prints the plan and confirms (skip with --yes), then moves each package independently with the same reinstall + rollback semantics; one failure doesn't stop the rest (exit 1 with a summary if any failed). --from matches the pinned version, so it works even after the old node version was nvm uninstalled — the go-to fix for "I deleted node 18 and my pins broke". | | nvmpin update <pkg>[@<ver>] | Update a pinned package in place: install npm's latest (or the given version) under the same pinned node version, rewrite shims (bin sets change between package versions), refresh the registry entry. The node version never changes — that's move's job, and passing --node says so. Because the install overwrites the old copy, a failure restores the previous version (reinstalled if the tree was already replaced) before reporting the error. | | nvmpin update --all | Update every pin to latest. Prints the plan with current versions and confirms (skip with --yes); each package updates independently with the same restore-on-failure semantics (exit 1 with a summary if any failed). | | nvmpin import | Bulk onboarding: pin every unpinned global package to the node version it is already installed under. Nothing is installed — import only writes shims and registry entries. Prints the plan and confirms (skip with --yes). Packages installed under multiple versions are never guessed at: they're skipped with a hint to add one explicitly. Packages without executables are skipped quietly; bin-name conflicts skip that package and exit 1 while the rest still import. Also re-adopts orphaned shims after a pins.json loss. | | nvmpin list [--json] | Table of pins: package, node version, bins, status (ok / broken shim / node version missing). | | nvmpin scan [--json] | Walk all installed nvm versions' global trees; report each package, which versions it exists in, duplicates, and unpinned candidates (npm/corepack excluded). | | nvmpin exec <pkg> -- <args...> | Run a pinned package's main bin directly with its pinned node — an escape hatch that bypasses PATH/shims. | | nvmpin doctor | Verify: shim dir on PATH and no pinned bin shadowed by an earlier nvm bin dir, every pinned node version still installed, shims match the registry (no orphans, no drift) and are executable, pins.json valid. Exits 2 with fix suggestions on findings. Doctor distinguishes findings (✗, exit 2) from notes (informational, exit 0): after nvm use, that version's bin dir precedes the shims in the live shell — expected and harmless unless it actually contains a pinned bin's name, so doctor prints a note instead of failing. To judge your rc ordering, run doctor in a fresh shell. With --fix, doctor repairs what pins.json unambiguously prescribes — creates the shim dir, regenerates missing/drifted/non-executable shims, removes genuinely untracked orphaned shims — then re-runs all checks, so the exit code reflects verified state. It never touches a corrupt pins.json, foreign files, rc files, or orphans while a registry backup exists (those may be recoverable pins — restore the backup or nvmpin import). |

Global flags: --yes (skip confirmations), --no-color (also respects NO_COLOR), --json (on list and scan), --help/-h.

JSON output: list --json prints an array of pin objects; scan --json prints an object with the installed versions and per-package report:

// nvmpin list --json
[
  { "package": "typescript", "node": "v18.20.4", "bins": ["tsc", "tsserver"],
    "pinnedAt": "2026-07-15T10:00:00.000Z", "status": "ok" }
]

// nvmpin scan --json
{ "nodeVersions": ["v20.19.5", "v18.20.4"],
  "packages": [
    { "package": "typescript", "versions": ["v18.20.4", "v20.19.5"],
      "duplicate": true, "pinned": "v18.20.4" }
  ] }

Node version syntax: 18, 18.20, v18.20.4 — resolved to the newest installed match. nvm aliases (lts/hydrogen, stable, default, …) are not supported; pass a number.

Exit codes: 0 ok · 1 user error · 2 environment error (or doctor problems) · 3 internal error. Set NVMPIN_DEBUG=1 for stack traces.

Environment: NVM_DIR (default ~/.nvm), NVMPIN_HOME (default ~/.nvmpin).

How it works

State lives in ~/.nvmpin:

~/.nvmpin/
  pins.json    # the registry: package -> { node version, bins, pinnedAt }
  bin/         # one bash shim per pinned executable, prepended to PATH

Each shim bakes in the absolute path to the pinned node version at generation time — no runtime lookups, no nvm invocation:

#!/usr/bin/env bash
# nvmpin shim for typescript -> v18.20.4 (do not edit)
exec "/Users/you/.nvm/versions/node/v18.20.4/bin/node" "/Users/you/.nvm/versions/node/v18.20.4/lib/node_modules/typescript/bin/tsc" "$@"

Because ~/.nvmpin/bin sits ahead of nvm's bin dir in PATH, the shim wins no matter which node version is active.

If pins.json gets corrupted: write commands (add, remove, move) back it up to pins.json.corrupt-<timestamp>, warn, and start fresh; read-only commands (list, scan, exec) refuse to touch it and exit 2 pointing at doctor, which reports the corruption (and any now-orphaned shims, with a pointer to the backup) without modifying anything.

Limitations

  • POSIX only. macOS and Linux with bash/zsh shims. No Windows support in v1.
  • No nvm aliases. --node lts/hydrogen is rejected; use a numeric version. Aliases move over time, which would silently break pins.
  • Native modules require nvmpin move. Switching a pin to a new node version is always a full reinstall, because compiled addons are ABI-specific. There is no fast re-point.
  • Deleting a node version breaks its pins. nvmpin doctor and nvmpin list will tell you; fix with nvmpin move.
  • Common engines.node ranges only. Exotic semver ranges in engines.node are never guessed at: they're skipped on the suggestion path, and produce a one-line "couldn't verify" warning when you passed --node explicitly.

Development

$ npm test                          # unit suite: offline, stub-based, fast
$ NVMPIN_INTEGRATION=1 npm test     # also runs the real-npm integration test
$ npm run test:integration          # same thing, spelled out

Releases are cut from CI — see RELEASING.md.

The integration test installs a small pinned package from the real npm registry into a tmp-dir sandbox to re-verify the npm behavior nvmpin's installer depends on (env-prefix precedence, config passthrough). It needs network access and takes a minute or two; it runs automatically on npm publish via prepublishOnly and shows as skipped in ordinary test runs.

License

MIT — see LICENSE.