Nub reads your project's package-manager pin, fetches that exact version from the npm registry — integrity-verified, cached under ~/.cache/nub/pm — and runs it under the project's Node. No corepack, no enable step, no baked version table: a six-month-old Nub binary 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 pin is read from two package.json fields at the workspace root, in order:

  1. packageManager — the corepack field. Exact only (pnpm@9.15.4, optionally +sha512.<hash>).
  2. devEngines.packageManager{ name, version }; the version may be a range.

Ranges belong in devEngines; corepack, pnpm, and Yarn all reject them in packageManager. With no pin in either field, nub pm which errors and points at nub pm use. A lockfile never implies a pin.

Multiple lockfiles

Two PMs' lockfiles side by side (pnpm-lock.yaml and package-lock.json) is ambiguous — Nub errors (ERR_NUB_LOCKFILE_AMBIGUOUS). Remove the stale one, or declare the pin with nub pm use.

Yarn Berry

A committed Berry release (.yarnrc.yml yarnPath) wins over the pin fields above. Nub runs the committed file directly and never provisions Berry, so a yarn@2+ pin without a committed release errors. Yarn classic (1.x) provisions like any other manager.

CLI

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 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    # exact

Moving 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:

  • package.json#/packageManager — the exact version plus a +sha512 hash from the verified tarball. What corepack, pnpm, and turbo execute; use is the only Nub command that writes it.
  • package.json#/devEngines/packageManager{ name, version: "^<exact>", onFail: "warn" }, the range-and-policy form npm and pnpm enforce natively, written beside the exact pin so the two can't drift.
  • The lockfile, in the new manager's format. A lockfile in another format is converted — resolution state preserved — and the old file removed. One already in the target format is left untouched; with no lockfile, the next install creates it. The converted lockfile passes the active manager's frozen install (pnpm install --frozen-lockfile, etc.) byte-for-byte.

Config is not migrated

Switching managers converts the lockfile, not your config. The .npmrc, any pnpm.* fields, pnpm-workspace.yaml settings, and other manager-specific config stay as they are — Nub does not translate them. Carry over whatever the new manager needs yourself.

Every file written or removed is named in the output; rerunning is a no-op. The refusals, all before anything is written:

  • Multiple foreign-format lockfiles — remove the stale ones first.
  • A binary bun.lockb source — regenerate it as text first with bun install --save-text-lockfile.
  • use yarn onto a Berry (2+) yarn.lock — classic would downgrade the format; use yarn set version.
  • use yarn on a graph using the workspace: protocol — classic yarn can't express it.

A converting use yarn is otherwise fine — Nub writes a classic yarn.lock directly. Running use bun writes the pin and 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.9

nub pm cache

Inspect or clear the package-manager cache.

$ nub pm cache
pnpm@9.15.4
yarn@1.22.22
$ nub pm cache clear

Versions live at ~/.cache/nub/pm/<pm>/<version>. A cached exact pin runs fully offline — the registry is only contacted to resolve ranges and fetch missing versions. Provisioned managers 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.

nub pm shim

The shims are an opt-in: run nub pm shim and a bare pnpm, npm, or yarn command routes through Nub to the pinned manager, with nub pm unshim to remove them. Default usage needs none of this — nub install and nub run already run the pin. See Package-manager shims for the install, the strict-by-default refusal, and the per-invocation overhead.

.npmrc

The PM download — packument and tarball — goes through your .npmrc: registry= picks the mirror, //host/:_authToken= authenticates. 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 — teams migrating off corepack keep their existing CI vars.