Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,61 @@ jobs:
echo "node-version output: ${{ steps.nub.outputs.node-version }}"
[[ "${{ steps.nub.outputs.node-version }}" == 20* ]] || { echo "::error::expected resolved pin 20.x"; exit 1; }

# Explicit node-version must bind later nub invocations, not only PATH node.
# A committed pin of 20 plus node-version: 22 is the matrix-job case: without
# NODE_EXECUTABLE, `nub` would still run 20.x from .node-version.
explicit-binds-nub:
name: explicit node-version binds later nub (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: fixture pinned to 20 (input will request 22)
shell: bash
# The provision step resolves the pin at the checkout root, where a
# consumer's pin lives — so the disagreeing .node-version must sit
# there too, not under a subdir.
run: |
echo "20" > .node-version
# scripts.ver exercises `nub run` (issue #12's repro). packageManager
# lets the same job assert a shimmed npm runs on the bound Node.
printf '{ "name": "explicit-binds", "private": true, "packageManager": "npm@10.9.2", "scripts": { "ver": "node -p process.versions.node" } }\n' > package.json
Comment thread
pullfrog[bot] marked this conversation as resolved.
- uses: ./
with:
node-version: "22"
shim: true
# packageManager on the fixture would auto-enable caching; this job
# is only about the NODE_EXECUTABLE bind.
package-manager-cache: false
- name: later nub uses 22.x, not the committed 20 pin
shell: bash
run: |
set -euo pipefail
echo "NODE_EXECUTABLE=${NODE_EXECUTABLE:-<unset>}"
[ -n "${NODE_EXECUTABLE:-}" ] || { echo "::error::NODE_EXECUTABLE was not exported"; exit 1; }
printf 'console.log(process.versions.node)\n' > "$RUNNER_TEMP/nub-ver.js"
nv="$(nub "$RUNNER_TEMP/nub-ver.js" | tr -d '\r' | tail -n1)"
echo "nub ran Node $nv"
[[ "$nv" == 22.* ]] || { echo "::error::nub ran Node $nv, expected 22.x (explicit node-version); the .node-version pin is 20"; exit 1; }
run_nv="$(nub run ver | tr -d '\r' | tail -n1)"
echo "nub run ver: $run_nv"
[[ "$run_nv" == 22.* ]] || { echo "::error::nub run used Node $run_nv, expected 22.x"; exit 1; }
path_nv="$(node -p 'process.versions.node' | tr -d '\r')"
echo "PATH node: $path_nv"
[[ "$path_nv" == 22.* ]] || { echo "::error::PATH node is $path_nv, expected 22.x"; exit 1; }
npm_path="$(command -v npm)"
echo "npm resolves to: $npm_path"
[[ "$npm_path" == *shims* ]] || { echo "::error::npm is not the nub shim: $npm_path"; exit 1; }
# Shimmed npm reports the Node it is running on. If the shim ignored
# NODE_EXECUTABLE and read the pin files, this would be 20.x.
shim_nv="$(npm version --json | node -p 'JSON.parse(require("fs").readFileSync(0,"utf8")).node' | tr -d '\r')"
echo "shimmed npm runs on Node $shim_nv"
[[ "$shim_nv" == 22.* ]] || { echo "::error::shimmed npm runs on Node $shim_nv, expected 22.x"; exit 1; }
echo "explicit node-version binds later nub — OK"

eager-provision:
name: eager provision (no input) installs the project pin (${{ matrix.os }})
strategy:
Expand All @@ -183,6 +238,8 @@ jobs:
nub node ls | grep -E '(^|[^0-9])20\.' || { echo "::error::eager provision did not install the pinned Node 20.x"; exit 1; }
echo "node-version output: ${{ steps.nub.outputs.node-version }}"
[[ "${{ steps.nub.outputs.node-version }}" == 20* ]] || { echo "::error::expected resolved pin 20.x, got ${{ steps.nub.outputs.node-version }}"; exit 1; }
# No explicit node-version input → do not override the project pin.
[ -z "${NODE_EXECUTABLE:-}" ] || { echo "::error::NODE_EXECUTABLE is set without an explicit node-version: '${NODE_EXECUTABLE}'"; exit 1; }

no-pin:
name: no project pin does not fail the action (${{ matrix.os }})
Expand Down Expand Up @@ -431,6 +488,7 @@ jobs:
set -euo pipefail
# provision-node: false leaves the node-version output empty.
[ -z "${{ steps.nub.outputs.node-version }}" ] || { echo "::error::node-version output is '${{ steps.nub.outputs.node-version }}' with provision-node: false, expected empty"; exit 1; }
[ -z "${NODE_EXECUTABLE:-}" ] || { echo "::error::NODE_EXECUTABLE is set with provision-node: false: '${NODE_EXECUTABLE}'"; exit 1; }
d="$RUNNER_TEMP/additive" && mkdir -p "$d/local-dep" && cd "$d"
cat > package.json <<'PKG'
{ "name": "additive", "version": "1.0.0", "dependencies": { "local-dep": "file:local-dep" },
Expand Down
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Because the eager provision reads the project's pin files off disk, **`actions/c
# after
- uses: nubjs/setup-nub@v0
with:
node-version: 20 # provisions Node 20 and fronts it on the global PATH
node-version: 20 # PATH node and later `nub` both use 20
cache: npm # accepted; caching is on by default regardless of value
registry-url: https://registry.npmjs.org
```
Expand All @@ -56,23 +56,21 @@ Like setup-node, **caching is on by default** — `cache: npm` (or `yarn`/`pnpm`

## Node on the global PATH

Like `actions/setup-node`, setup-nub provisions a Node toolchain and **adds its bin dir to the global `PATH`**, so bare `node`/`npm`/`npx`/`corepack` in subsequent steps resolve to that version — a swap from `actions/setup-node@v4` to `nubjs/setup-nub@v0` leaves later steps that call bare `node`/`npm` behaving the same. The provisioned bin holds the real Node binaries (no nub-branded shim is fronted), and it is placed ahead of `nub`'s own bin so real `npm`/`npx` win.
Like `actions/setup-node`, setup-nub provisions a Node toolchain and **adds its bin dir to the global `PATH`**, so bare `node`/`npm`/`npx`/`corepack` in subsequent steps resolve to that version — a swap from `actions/setup-node@v4` to `nubjs/setup-nub@v0` leaves later steps that call bare `node`/`npm` behaving the same. The provisioned bin holds the real Node binaries (no nub-branded shim is fronted), and it is placed ahead of `nub`'s own bin so real `npm`/`npx` win. An explicit `node-version` / `node-version-file` also exports `NODE_EXECUTABLE` so later `nub` / `nub run` use that same binary, instead of re-resolving the project's pin.

Which version is fronted:

- **With an explicit `node-version` / `node-version-file`** — that version is authoritative for the PATH (matching setup-node): it is provisioned and its bin dir is fronted. `nub` itself still runs the **project's** declared pin at invocation time; if the two differ, the action emits a warning.
- **With no input (the common case)** — the project's own pinned Node is resolved (`devEngines.runtime` → `.node-version` → `.nvmrc` → `engines.node`), provisioned up front so later steps don't pay a lazy download, and its bin dir is fronted.
- **With an explicit `node-version` / `node-version-file`** — that version is authoritative for the rest of the job (matching setup-node): it is provisioned, its bin dir is fronted on PATH, and `NODE_EXECUTABLE` is exported so later `nub` / `nub run` use the same binary without rewriting pin files on disk.
- **With no input (the common case)** — the project's own pinned Node is resolved (`devEngines.runtime` → `.node-version` → `.nvmrc` → `engines.node`), provisioned up front so later steps don't pay a lazy download, and its bin dir is fronted. Later `nub` invocations keep following the project's pin.
- **No pin and no input** — nothing is provisioned and nothing is fronted (no error); the runner's preinstalled Node stays on PATH and nub provisions lazily at runtime.

The remaining nuance vs setup-node: when an explicit `node-version` is set, it governs the **global PATH**, but `nub <script>` / `nub run` still execute the **project's** declared Node (nub's runtime is pin-authoritative). For most workflows these agree; the action warns when they don't.

## Inputs

| Input | Default | Behavior |
|---|---|---|
| `nub-version` | `latest` | Version of nub to install — any npm semver range (`0.0.47`, `^0.0`, `latest`). |
| `node-version` | — | Provision this Node and front its bin dir on the global PATH (authoritative for PATH, like setup-node). `nub` itself still runs the project pin at invocation; warns on mismatch. |
| `node-version-file` | — | Read a Node version from this file (`.node-version`, `.nvmrc`, `package.json`), provision it, and front it on PATH. |
| `node-version` | — | Provision this Node, front its bin dir on the global PATH, and bind later `nub` invocations to it via `NODE_EXECUTABLE` (no pin-file rewrite). |
| `node-version-file` | — | Read a Node version from this file (`.node-version`, `.nvmrc`, `package.json`), provision it, front it on PATH, and bind later `nub` invocations to it. |
| `cache` | auto | Explicitly enable/disable caching of nub's global store and provisioned Node toolchains. A **boolean**; a setup-node PM name (`npm`/`yarn`/`pnpm`/`bun`) is also accepted and treated as truthy. Leave **unset** to auto-enable when the project looks installable (mirrors setup-node). |
| `package-manager-cache` | `true` | Set to `false` to disable the automatic caching. Mirrors setup-node's input of the same name. An explicit `cache` value still wins. |
| `cache-dependency-path` | auto-detect | Lockfile path(s) whose hash keys the cache. Globs / newline-delimited lists. |
Expand All @@ -91,7 +89,7 @@ Accepted for setup-node compatibility but **ignored** (never errors): `check-lat

## Package-manager shims

With `shim: true`, the action runs `nub pm shim` and puts the shim directory first on PATH. In a project that pins a package manager (`packageManager` or `devEngines.packageManager`), `npm`, `pnpm`, and `yarn` in later steps run that pinned version, provisioned on demand, the same job corepack does. 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.
With `shim: true`, the action runs `nub pm shim` and puts the shim directory first on PATH. In a project that pins a package manager (`packageManager` or `devEngines.packageManager`), `npm`, `pnpm`, and `yarn` in later steps run that pinned version, provisioned on demand, the same job corepack does. 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. An explicit `node-version` / `node-version-file` still applies: those later steps inherit `NODE_EXECUTABLE`, so a shimmed `pnpm`/`npm`/`yarn` runs on the requested Node.
Comment thread
pullfrog[bot] marked this conversation as resolved.

```yaml
- uses: nubjs/setup-nub@v0
Expand All @@ -105,7 +103,7 @@ With `shim: true`, the action runs `nub pm shim` and puts the shim directory fir
| Output | Description |
|---|---|
| `nub-version` | The installed nub version (bare `v<semver>`). |
| `node-version` | The Node version nub resolves for the project. Empty when nothing was provisioned. |
| `node-version` | The Node version this action provisioned (the explicit input, or the project's pin). Empty when nothing was provisioned. |
| `cache-hit` | Whether an exact cache match was restored — `true` on an exact key hit, `false` on a `restore-keys` partial hit, empty on a full miss (e.g. the first run), mirroring `actions/cache`. |
| `caching-enabled` | Whether caching is active for this run (`true`/`false`), reflecting the resolved `cache`/`package-manager-cache`/auto-detect decision — independent of whether a cache was hit. |

Expand Down
Loading