Skip to content

Front the provisioned Node on the global PATH (setup-node executable parity)#5

Merged
colinhacks merged 1 commit into
mainfrom
path-parity
Jun 22, 2026
Merged

Front the provisioned Node on the global PATH (setup-node executable parity)#5
colinhacks merged 1 commit into
mainfrom
path-parity

Conversation

@colinhacks

Copy link
Copy Markdown
Contributor

Why

setup-nub advertises a drop-in for actions/setup-node, but it never put the provisioned Node's bin dir on the global PATH. actions/setup-node does (core.addPath(<toolcache>/node/<ver>/bin)), so bare node/npm/npx/corepack in downstream steps resolve to the specified version. Under setup-nub they resolved to the runner's preinstalled Node — non-deterministic across images and not the requested version. This closes that gap so a swap of actions/setup-node@v4nubjs/setup-nub@v0 leaves later steps that call bare node/npm behaving the same.

What changed

  • Global PATH front. After provisioning, the nub-resolved Node bin dir is written to $GITHUB_PATH. The dir is derived at runtime from nub node which (never a hardcoded cache path) and is correct on every OS by construction — dirname of the resolved node binary (POSIX <root>/bin/node; Windows node.exe at the version root, confirmed against nub's source). The bin dir is fronted ahead of nub's own bin so real npm/npx win over any nub PM shim.
    • Host-fallback discrimination is OS-independent. nub node which prints the resolution reason on stderr; resolved from node on PATH means nub fell back to the runner's host Node (nothing provisioned) — in that case nothing is fronted (and the node-version output is empty, matching the documented contract, rather than leaking the host version).
    • Explicit node-version/node-version-file is authoritative for the PATH (matching setup-node): it is provisioned and fronted even when the project's own pin differs. nub itself still runs the project pin at invocation time and the action warns on a mismatch.
  • Problem matchers. Registers the same three matchers setup-node ships (tsc, eslint-stylish, eslint-compact) so tsc/eslint output surfaces as inline annotations.
  • NODE_AUTH_TOKEN re-export. Mirrors setup-node's authutil: when present, re-exported to the env (heredoc form, masked from logs).
  • .npmrc "byte-for-byte" claim corrected to "functionally equivalent" in the comment + README (line order differs; npm parses both identically).
  • Smoke jobs. New 3-OS differential path-parity job asserts bare node/npm/npx resolve to the specified version inside the fronted dir (plus npm i -g reachability and a corepack presence probe), against an actions/setup-node control.

Design notes

  • The PATH approach is raw provisioned bin (the audit's decision (a)): the most direct "everything works the same," brand-clean (real node/npm/npx, nothing nub-branded fronted), and consistent with nub's node-PATH-hijack contract. The version-resolving-shim alternative was considered; it only matters if the fronted node should re-resolve the pin dynamically mid-job, which setup-node parity does not require.
  • The host-fallback discriminator couples to nub's stderr reason string (resolved from node on PATH). It is a documented contract, but a nub-side test asserting that phrase would harden it; a structured nub node which --json signal would remove the coupling entirely (follow-up).

Verification

  • actionlint clean (only 4 pre-existing SC2193/SC2016 template false-positives in unrelated jobs; zero new).
  • shellcheck clean on the changed run: blocks.
  • Logic verified locally against real nub across 3 cases: explicit node-version fronts the specified version's bin + warns on pin mismatch; project-pin fronts the resolved pin; no-pin host-fallback correctly skips fronting and emits an empty node-version.
  • Windows node.exe-at-version-root layout confirmed against nub source (crates/nub-core/src/node/discovery.rs, version_management/mod.rs), so dirname is correct without OS branching.
  • The Windows leg of the new smoke job is the load-bearing CI surface — watch it on first run.

… auth re-export

Previously the action provisioned Node into nub's cache but never added its bin
dir to the global PATH, so bare node/npm/npx/corepack in later steps resolved to
the runner's preinstalled Node rather than the specified version — diverging from
actions/setup-node, whose toolcache bin dir IS PATH-fronted.

- Front the nub-resolved Node bin dir onto $GITHUB_PATH. The dir is derived at
  runtime from `nub node which` (never a hardcoded cache path) and is correct on
  every OS by construction: dirname of the resolved node binary (POSIX bin/node,
  Windows node.exe at the version root). The stderr resolution reason is the
  OS-independent discriminator for host-fallback (skip fronting) vs a managed
  Node (front it). An explicit node-version/node-version-file is authoritative
  for the PATH front (matching setup-node); nub still runs the project pin at
  invocation time and warns on a mismatch. The bin dir is fronted ahead of nub's
  own bin so real npm/npx win.
- node-version output is now empty on host fallback (nothing provisioned) instead
  of leaking the runner's host version, matching the documented contract.
- Register the same three problem matchers setup-node ships (tsc, eslint-stylish,
  eslint-compact) so tsc/eslint output surfaces as inline annotations.
- Re-export NODE_AUTH_TOKEN to the env when present (matching setup-node authutil),
  via the heredoc form and masked from logs.
- Correct the .npmrc 'byte-for-byte' claim to 'functionally equivalent' in the
  action comment and README (line order differs; npm parses both identically).
- Add a 3-OS differential smoke job asserting bare node/npm/npx resolve to the
  specified version inside the fronted dir (plus npm i -g reachability), against
  an actions/setup-node control.
Copilot AI review requested due to automatic review settings June 22, 2026 19:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@colinhacks colinhacks merged commit 47e5e73 into main Jun 22, 2026
36 checks passed
@colinhacks colinhacks deleted the path-parity branch June 22, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants