Package meta-manager
Provision and run the package manager your project pins — corepack's job, in native Rust. Pin a version and the exact pnpm/npm/yarn is fetched, verified, cached, and run on the project's Node.
Nub reads your project's package-manager pin, downloads that exact version from the npm registry — integrity-verified before extraction, cached under ~/.cache/nub/pm — and runs it under the project's Node. No corepack, no enable step, no baked version table: a nub binary from six months ago provisions today's pnpm.
$ nub pm which
/Users/you/.cache/nub/pm/pnpm/9.15.4/package/bin/pnpm.cjs
» resolved from packageManager (pnpm@9.15.4)Pin resolution
The pinned package manager resolves from three sources, in order:
.yarnrc.ymlyarnPath— a committed Yarn Berry release. Run directly; nothing to provision.package.json#packageManager—name@x.y.z, optionally+sha512.<hash>. The corepack field; Nub is contract-compatible with it.package.json#devEngines.packageManager—{ name, version }, whereversionmay be a range.
Pins are read at the workspace root. The packageManager field must be exact — the entire ecosystem enforces this (corepack, pnpm, yarn all reject ranges there); ranges belong in devEngines. With no pin, there's nothing to manage — nub pm which says so and points at nub pm use.
Yarn Berry
Yarn 2+ ships as a release committed to your repo (.yarnrc.yml yarnPath) — Nub runs that file directly and doesn't provision Berry: a yarn@2+ pin without a committed release is an error. Yarn classic (1.x) provisions like any other package manager.
nub pm which
Print the resolved package manager — path on stdout, provenance on stderr, so PM=$(nub pm which) captures just the path. Provisions the pinned version if it isn't cached yet.
$ nub pm which
/Users/you/.cache/nub/pm/pnpm/9.15.4/package/bin/pnpm.cjs
» resolved from packageManager (pnpm@9.15.4)nub pm use
Declare the project's package manager — npm, pnpm, yarn, or bun. One command sets the identity and makes the artifacts agree: it resolves the version (exact, range, or dist-tag; bare means latest), fetches and verifies it, writes packageManager, and aligns the lockfile.
nub pm use pnpm # newest pnpm
nub pm use npm@10 # newest 10.x
nub pm use pnpm@9.15.4 # exactMoving an npm project to pnpm:
$ nub pm use pnpm
Fetching pnpm 11.5.3 (4 MB)...
using pnpm@11.5.3
package.json: packageManager = pnpm@11.5.3 (+sha512)
package.json: devEngines.packageManager = { name: "pnpm", version: "^11.5.3", onFail: "warn" }
pnpm-lock.yaml: written (converted from package-lock.json)
package-lock.json: removed (migrated)What it writes:
packageManager— the exact version plus a+sha512hash computed from the verified tarball (never copied from registry metadata). This is what corepack, pnpm, and turbo execute, anduseis the only nub command that writes it.- The lockfile, in the new manager's format. An existing lockfile in another format is converted — resolution state preserved, never deleted-and-regenerated — and the old file removed. Already the right format: kept as is. No lockfile: nothing; the next install creates it. The converted lockfile passes the real manager's frozen install (
pnpm install --frozen-lockfile,npm ci,bun install --frozen-lockfile) byte-for-byte. devEnginesis never created. If it already exists and names a different manager, itsnameis reconciled (version dropped —packageManageralone pins exactly).
Every file written or removed is named in the output; rerunning is a no-op. Two refusals, both before anything is written: multiple foreign-format lockfiles (nub won't guess which carries the real resolution state — remove the stale ones first), and use yarn when it would have to convert (nub doesn't write yarn.lock yet; generate it with yarn install, then rerun — with yarn.lock in place only the declaration is written). Running use bun writes the declaration and the lockfile but doesn't provision bun.
nub pm update
Bump the pin: resolve the newest version satisfying the devEngines range (or the registry latest if there's no range), provision it, and rewrite packageManager with a fresh hash. Alias: nub pm up.
$ nub pm update
Fetching pnpm 9.15.9 (4 MB)...
updated pnpm 9.15.4 → 9.15.9nub pm cache
Inspect or clear the package-manager cache.
$ nub pm cache
pnpm@9.15.4
yarn@1.22.22
$ nub pm cache clearVersions live at ~/.cache/nub/pm/<pm>/<version>. An exact pin that's already cached runs fully offline — the registry is only contacted to resolve ranges and fetch missing versions. Provisioned package managers also run with a warm V8 compile cache (NODE_COMPILE_CACHE), so the PM's multi-megabyte bundle loads as cached bytecode instead of re-parsing on every call.
Private registries
The PM download — packument and tarball — goes through your .npmrc: registry= picks the mirror, //host/:_authToken= authenticates against it. An auth-required Artifactory/Nexus mirror works with the config you already have:
registry=https://npm.corp.example
//npm.corp.example/:_authToken=${CORP_NPM_TOKEN}To fetch package managers from a different registry than your dependencies, COREPACK_NPM_REGISTRY (+ COREPACK_NPM_TOKEN) overrides the PM-download registry specifically — teams migrating off corepack keep their existing CI vars.
nub pm shim
Make bare npm / pnpm / yarn commands (plus npx, pnpx, yarnpkg) use the project's pinned package manager — typed from muscle memory, or spawned by tools you don't control. This is what corepack enable does, without the corepack tax: each shim is the nub binary (a hardlink), so deciding which PM to run costs zero Node boots.
$ nub pm shim
created 7 shims in ~/.nub/shims
added PATH entry to ~/.zshrc — restart your shell or: source ~/.zshrcWith shims active, a bare command resolves against the project it runs in:
- Pinned, matching name — runs the pinned version, provisioning it on first use. A cached pin runs fully offline.
- Pinned, different name — refuses:
npm installin a pnpm project would write a competingpackage-lock.json; the error names the pinned manager and the command to paste. (npx,pnpx, and theinit/create/dlx/execverbs are exempt —npm create viteworks anywhere.) - Unpinned — falls through to the package manager already on your
PATH, exactly as if the shim weren't there. If there is none, nub provisions a sensible default (matched to your lockfile's format, else latest) and says so.
Strictness applies to commands you type. When a package manager is already running above the shim — a lifecycle script shelling out to a different PM, detected via npm_config_user_agent — a name mismatch falls through to the system PM instead of refusing, so a pnpm postinstall that calls npm doesn't break an install you never issued.
The shim never writes to your package.json. Re-run nub pm shim after upgrading nub to refresh the links.
nub pm unshim
Remove the shims and the PATH entry. Works even after the nub binary itself has been uninstalled — the shim directory carries its own copy for exactly this cleanup.
$ nub pm unshim
removed ~/.nub/shims · PATH entry stripped from ~/.zshrcNode managernub node
Manage the Node versions Nub provisions — pin a version and it's fetched automatically, or drive the cache explicitly with nub node install / ls / uninstall / pin.
FAQ
The short, indexed answers to common questions about Nub — what it is, how it works, and what it deliberately is not.