GitHub Action
A drop-in replacement for the official setup-node action. Swap one line in your workflow and Nub installs itself, provisions the project's pinned Node, and fronts it on PATH.
A drop-in for actions/setup-node — swap one line:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: nubjs/setup-nub@v0
- run: nub install
- run: nub run testEverything that follows resolves to the project's pinned Node — the same contract as setup-node. Bare node, npm, and npx in later steps run the pinned version, because the action fronts that Node's bin directory on the runner's global PATH.
- uses: actions/checkout@v4
- uses: nubjs/setup-nub@v0
- run: node --version # the project's pinned Node, not the runner default
- run: nub installRead the full docs on github.com. This action accepts the same inputs.
How it works
The action installs the nub CLI from the release archive for the runner's platform, verified against the release's sha256 sidecar, into the runner tool cache, then provisions the project's pinned Node into Nub's cache during the setup step, before any of your steps run. If the archive install fails, the action falls back to npm install -g @nubjs/nub. The pin is resolved from the project's Node-version sources, in this order:
package.json#/devEngines/runtime.node-version.nvmrc.tool-versions(the asdf/mise file'snodejsornodeline)package.json#/engines/node
That resolved Node's bin directory is added to the global PATH, so bare node/npm/npx and every subsequent step see the pinned version. With no pin declared, the eager step skips and Nub provisions at runtime.
Because the pin lives in the repository, actions/checkout must run before this action.
Beside setup-node
The action also runs after actions/setup-node in a workflow that keeps it. The install line is what changes:
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- uses: nubjs/setup-nub@v0
with:
provision-node: false
- run: npm ci
- run: nub install --frozen-lockfile
- run: npm testnub install --frozen-lockfile reads the existing package-lock.json unchanged, and npm test runs the real npm as before. provision-node: false leaves the Node that setup-node put on PATH in place; without it this action fronts the project's own pin. The cache: npm line can stay: it restores npm's own cache, which Nub does not read, and this action caches Nub's store by default.
setup-node, with Nub installed
nubjs/nub/setup-node takes the inputs and outputs of actions/setup-node and installs nub beside it:
- uses: actions/checkout@v4
- uses: nubjs/nub/setup-node@v0
- run: npm ci
- run: npm testTwo defaults differ from the action above. With no node-version input, the runner's Node stays when the project's pin allows it or when there is no pin; only a pin that excludes the runner's Node is downloaded and put first on PATH, with a notice in the job log. And the package-manager shims are on by default: npm, pnpm and yarn in later steps run the version the project pins in packageManager or devEngines, provisioned on demand. provision-node: false and shim: false turn each off. The inputs are listed in its README.
The install step only
nubjs/nub/install replaces the line that runs npm ci, pnpm install --frozen-lockfile, yarn install --immutable or bun install --frozen-lockfile, and changes nothing else:
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- uses: nubjs/nub/install@v0
- run: npm testNub reads the lockfile unchanged, installs it into node_modules, and caches its store across runs, keyed on the lockfile. Node and the package managers on PATH are left alone. working-directory points the install at a subdirectory, frozen-lockfile: false lets it update the lockfile as npm install does, and shim: true adds the package-manager shims for later steps. The inputs are listed in its README.
npm ci, on Nub
nubjs/nub/npm-ci is the npm ci step for a project with a package-lock.json. It installs exactly what the lockfile records, under npm ci's own contract, and leaves the lockfile as it found it:
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- uses: nubjs/nub/npm-ci@v0
with:
lockfile: package-lock.json
- run: npm testEvery package is installed at the version and from the resolved URL the lockfile records and checked against its integrity hash; nothing is resolved against the registry. node_modules is removed first, the tree is hoisted as npm lays it out, every lifecycle script runs as it does under npm and under the job's node (a .nvmrc or engines pin in the project is not consulted and no Node is downloaded), and the install fails when the lockfile and package.json disagree. The action hashes the lockfile before and after the install and fails if a byte changed; the hash is its lockfile-sha256 output. args takes npm ci's own flags (--omit=dev, --ignore-scripts); a flag the engine does not honor fails the action rather than running npm.
install takes any lockfile and applies Nub's own layout and build-script posture. npm-ci applies npm's, so a workflow that ran npm ci behaves as it did. The inputs are listed in its README.
Inputs
| Input | Default | Description |
|---|---|---|
nub-version | latest | Version of Nub to install — any semver range npm understands. |
node-version | Provision this version and front it on PATH instead of the project pin. | |
node-version-file | Read a version from a file and front it on PATH instead of the project pin. | |
cache | auto | Cache Nub's store and provisioned Node toolchains. Auto-enables on a lockfile or packageManager/devEngines; an explicit value wins. |
package-manager-cache | true | Set false to turn off the automatic caching above. |
cache-dependency-path | Lockfile path(s) whose hash keys the cache. | |
cache-key-prefix | Prefix injected into the cache key to scope or bust caches independently. | |
working-directory | checkout root | Directory to resolve the pin and lockfile from, for monorepo subdirectories. |
registry-url | Registry to set up for auth; writes a temporary user-level .npmrc via NPM_CONFIG_USERCONFIG. | |
scope | Scope for a scoped registry; falls back to the repository owner for GitHub Packages. | |
always-auth | false | Authenticate on every registry request. |
token | github.token | Token for GitHub-API rate-limit relief when resolving Nub's version range. |
shim | false | Run nub pm shim after install and put the shim directory first on PATH, so npm, pnpm, and yarn in later steps run the package manager the project pins. |
provision-node | true | Set false to leave Node alone: nothing provisioned, nothing fronted on PATH, and the node-version output empty. For a job where actions/setup-node already ran. |
The setup-node inputs check-latest, architecture, mirror, and mirror-token are accepted and ignored. The version input is a deprecated alias for nub-version and emits a warning.
nub-version
Pin the CLI version to install — any semver range npm understands. Defaults to the latest release:
- uses: nubjs/setup-nub@v0
with:
nub-version: ^0.1.0node-version
Front a specific version on PATH for this run instead of the project pin. The action provisions it during setup and puts it on the global PATH, so bare node downstream is this version:
- uses: nubjs/setup-nub@v0
with:
node-version: 26.10.0Nub still runs the project's declared pin when invoked as nub, so the action warns if this input differs from that pin. Omit it to let Nub resolve and provision the project pin automatically.
node-version-file
Read a Node version from a file, then provision and front it on PATH. Accepts .node-version, .nvmrc, and package.json (reads devEngines.runtime, then engines.node):
- uses: nubjs/setup-nub@v0
with:
node-version-file: .node-versioncache
Caching is on by default when the project has a lockfile or a package.json that declares packageManager or devEngines, mirroring setup-node. Set the input explicitly to override:
- uses: nubjs/setup-nub@v0
with:
cache: true # force on; `false` forces offA package-manager name (npm/pnpm/yarn/bun) is accepted for setup-node compatibility and treated as on — Nub keeps one store regardless of package manager.
The cache key is derived from the project's lockfile and the Node pin, with the lockfile auto-detected in this order:
pnpm-lock.yamlpackage-lock.jsonbun.lockbun.lockbyarn.lock
package-manager-cache
Turns the automatic caching above off without setting cache:
- uses: nubjs/setup-nub@v0
with:
package-manager-cache: falsecache-dependency-path
Override the auto-detected lockfile for cache keying — a lockfile path, glob, or newline-delimited list:
- uses: nubjs/setup-nub@v0
with:
cache-dependency-path: |
app/pnpm-lock.yaml
packages/*/pnpm-lock.yamlA restore-keys ladder gives a partial warm hit even when the lockfile changes.
cache-key-prefix
Inject a prefix into the cache key (nub-<os>-<arch>-<prefix>-<hash>) to scope or bust caches independently of the lockfile:
- uses: nubjs/setup-nub@v0
with:
cache-key-prefix: v2working-directory
Resolve the Node pin and lockfile from a subdirectory rather than the checkout root — for monorepos where package.json/.node-version live below the root:
- uses: nubjs/setup-nub@v0
with:
working-directory: apps/webregistry-url
Set up authenticated registry access. The action writes a temporary user-level .npmrc via NPM_CONFIG_USERCONFIG and wires the auth token to NODE_AUTH_TOKEN:
- uses: nubjs/setup-nub@v0
with:
registry-url: https://registry.npmjs.org
- run: nub install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}For GitHub Packages, point at the registry and the scope defaults to the repository owner:
- uses: nubjs/setup-nub@v0
with:
registry-url: https://npm.pkg.github.com
- run: nub install
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}Read the full docs on docs.github.com.
scope
Scope for a scoped registry. Falls back to the repository owner for npm.pkg.github.com. The .npmrc written by registry-url follows the same contract as setup-node.
always-auth
Write always-auth=true into the .npmrc to authenticate on every registry request.
token
Token for GitHub-API rate-limit relief when resolving Nub's version range (and Node downloads on GHES). Defaults to github.token on github.com.
provision-node
Leave Node alone. Nothing is provisioned, nothing is fronted on PATH, and the node-version output is empty. For a job where actions/setup-node already put the wanted Node on PATH; nub still resolves the project's pin at its own invocation:
- uses: actions/setup-node@v4
with:
node-version: 26.10.0
- uses: nubjs/setup-nub@v0
with:
provision-node: falseshim
Run nub pm shim after install and put the shim directory first on PATH. In a project that pins a package manager through packageManager or devEngines.packageManager, npm, pnpm, and yarn in later steps run that pinned version, provisioned on demand. In an unpinned project they fall through to the runner's own tool. The shims do not route those commands into Nub's own installer; use nub install for that.
- uses: nubjs/setup-nub@v0
with:
shim: true
- run: pnpm install --frozen-lockfile # the version packageManager pinsOutputs
| Output | Description |
|---|---|
nub-version | The installed Nub version. |
node-version | The Node version provisioned during setup; empty when nothing was provisioned. |
cache-hit | Whether an exact store-cache match was restored; empty on a miss, mirroring actions/cache. |
caching-enabled | Whether caching is active for this run, independent of whether a cache was hit. |
Differences from setup-node
Two behaviors differ:
- The
cacheinput is a boolean rather than a package-manager name. A package-manager name still works (it is treated as on), since Nub keeps a single store regardless of package manager. - The registry
.npmrcis written fresh to$RUNNER_TEMPand pointed at viaNPM_CONFIG_USERCONFIG, rather than merged into the user.npmrc. Both are parsed identically by npm and Nub's package manager.
Related
- Node manager — how Nub provisions and pins Node versions.
- Package manager — what
nub installreads, including the.npmrcthe action writes.