Skip to content

feat(node): auto-provision Node when there is no pin and no node on PATH#296

Merged
colinhacks merged 2 commits into
mainfrom
issue-294-no-node-autoprovision
Jul 3, 2026
Merged

feat(node): auto-provision Node when there is no pin and no node on PATH#296
colinhacks merged 2 commits into
mainfrom
issue-294-no-node-autoprovision

Conversation

@colinhacks

Copy link
Copy Markdown
Contributor

On a fresh machine (nub installed before any Node), nub <file> / nub watch / hijack-node dead-ended with no Node binary found on PATH, and the error pointed at bare nub node install, which itself errors with no pin.

The runner path now auto-provisions when there's no pin and no node on PATH: store-first (highest supported cached version — silent, no download; sub-floor skipped), else download latest. Unpinned latest stays a moving target — no pin written; nub node pin <version> opts into a reproducible one. The NoNodeOnPath message (still shown on status paths) now suggests nub node pin <version>.

Docs updated. Verified e2e; fmt/clippy/tests green locally.

Closes #294

On a fresh machine — nub installed before any Node — `nub <file>`,
`nub watch`, and the hijack-`node` path no longer dead-end with
"no Node binary found on PATH". The runner provisioning path now
auto-provisions when there is no project pin and no node on PATH:
store-first (reuse the highest supported version already cached, zero
download), else download `latest` from nodejs.org. Sub-floor cached
versions are skipped so an unpinned run downloads a working Node rather
than failing the version floor.

Unpinned `latest` is a deliberate moving target — no pin is written;
`nub node pin <version>` opts into a reproducible version. The
NoNodeOnPath message (still shown on non-provisioning status paths like
`nub node which`) now suggests the working `nub node pin <version>`
instead of the dead-ending bare `nub node install`.

Closes #294

Claude-Session: https://claude.ai/code/session_01Xuh9u8b93eMNu53fTQTWX7
Copilot AI review requested due to automatic review settings July 3, 2026 21:07
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nub Ready Ready Preview, Comment Jul 3, 2026 9:34pm

Request Review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — auto-provisioning Node on the zero-config runner path when there is no project pin and no node on PATH (the fresh-machine case, #294), plus the matching docs.

  • Route no-pin/no-node to provisioningdiscover_or_provision_node no longer returns NoNodeOnPath when the pin chain is empty; it calls the new provision_unpinned_node(). Verified the no-pin branch of discover_node yields NoNodeOnPath only when PATH genuinely has no node, so the routing is sound.
  • Add provision_unpinned_node() — store-first via highest_store_node() (silent, no download), else download latest with pin_source: None so no pin is written and pin-disagreement warnings stay quiet.
  • Add highest_store_node_in — highest cached version filtered to is_supported(), so a sub-floor-only store (e.g. a leftover nub node pin 16) falls through to downloading latest rather than dead-ending on Unsupported.
  • New UnpinnedProvisionFailed error variant — distinct from ProvisionFailed because there's no pin to name; the NoNodeOnPath message now points at nub node pin <version> (the remedy that actually works from a fresh machine).
  • Docsnode.mdx documents the fresh-machine flow and updates the precedence list's fallback entry.

The floor is respected on both unpinned branches (store-first filters is_supported; the download resolves latest), so the one caller that skips check_min_version (exec_under_project_node) is unaffected. The Using Node.js … (resolved from latest) / Installing from nodejs.org… strings in the docs console example match the real output in version_management/mod.rs. Tests cover the store-first/fall-through matrix and the reworded remedy. Clean, minimal, and correct.

Pullfrog  | View workflow run | Using Claude Opus𝕏

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.

Pull request overview

Fixes the fresh-machine “nub installed before Node” dead-end by teaching the file-run / watch / hijack-node path to auto-provision Node when there’s no project pin and no node on PATH (store-first, else download latest). This addresses #294.

Changes:

  • Add an unpinned auto-provisioning path in Node discovery (prefer highest supported cached Node; otherwise resolve/download latest).
  • Update discovery error surfaces (new UnpinnedProvisionFailed, revised NoNodeOnPath wording) and add a unit test for store-first selection behavior.
  • Document the fresh-machine behavior in the Node manager docs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
site/content/docs/node.mdx Documents the new no-pin/no-PATH-node behavior and updates version precedence wording.
crates/nub-core/src/node/discovery.rs Implements unpinned auto-provisioning (store-first → else download latest), adds a new error variant, and extends tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 10 to +21
Drop a `.node-version` or `.nvmrc` in your project (or an `engines.node` range in `package.json`) and run `nub`. If that Node is already on your machine it's used as-is; if not, Nub downloads the matching stock build from nodejs.org — SHA-256 verified and cached under `~/.cache/nub/node` — then runs your code. Nub provisions the pinned version itself rather than handing off to `nvm`, `fnm`, or `mise`; with nothing pinned anywhere up the tree, it uses whatever `node` is already on your `PATH`.

```console
$ echo {{NODE_VERSION}} > .node-version
$ nub index.ts
Using Node.js {{NODE_VERSION}} (resolved from .node-version)
Installing from nodejs.org... (24 MB)
Installed in 8.0s
# ...your script runs, on exactly the Node you pinned
```

On a fresh machine with no Node at all — you installed Nub before anything else — a plain `nub file.ts` still runs. With no pin and no `node` on `PATH`, Nub provisions the latest release and runs your code on it, reusing the newest version already in its cache before downloading. Pin a version whenever you want a reproducible one (`nub node pin <version>`); until then, each run picks up the latest.
Comment on lines +98 to +102
#[error(
"no Node on PATH and no project pin — tried to provision the latest Node and failed: {reason}\n\
\x20\x20Check your network / proxy, or install Node and put it on PATH."
)]
UnpinnedProvisionFailed { reason: String },
Comment on lines +371 to +375
/// Unpinned `latest` is a deliberate moving target: no pin is written, so each
/// fresh-machine run picks up the newest secure release. A project that wants a
/// reproducible version pins one (`nub node pin <version>`), which routes through
/// the pinned provisioning path instead. `pin_source` stays `None` — the run is
/// genuinely unpinned, so downstream pin-disagreement warnings don't fire.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not sure why the commit suggestion button is green for me.

image

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@colinhacks see above

registry_config/npmrc_value reads project_root/.npmrc + $HOME/.npmrc, never
the cache-store root — the test wrote its dead-registry override to
cache/nub/.npmrc, which was never consulted. It passed on main only because
an unrelated no-pin/no-node hard error also produced a nonzero exit code;
this PR's auto-provision fix removes that error and unmasked the latent
test bug. Write the override to the project's own .npmrc instead, so the
test exercises the dead-registry rejection it was meant to.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — one new commit since the prior pullfrog review (7ed4b6f): a fix to the PM-shim provisioning test that makes its dead-registry assertion actually load-bearing.

  • Fix dead-registry location in unpinned_path_miss_provisions_a_dynamic_default_within_the_lockfile_family — the test wrote its dead-registry .npmrc to the cache-store root (cache/nub/.npmrc), which the provisioning config path never reads, so the dead registry was inert and the failure was for the wrong reason. The commit relocates it to the project root (proj/.npmrc).

Verified the relocation is correct: pm::registry::registry_config resolves the base via workspace::scripts::npmrc_value(project_root, "registry"), which reads project_root/.npmrc then $HOME/.npmrc only — never the cache-store root. With HOME pointed at an empty dir and proj/.npmrc set to http://127.0.0.1:1/, the provision attempt now fails fast (connection refused) with zero real network, exactly as the assertions expect. The comment update accurately documents the config-resolution contract.

Pullfrog  | View workflow run | Using Claude Opus𝕏

@colinhacks colinhacks merged commit 1fa8fb1 into main Jul 3, 2026
59 checks passed
@colinhacks colinhacks deleted the issue-294-no-node-autoprovision branch July 3, 2026 21:55
@colinhacks

Copy link
Copy Markdown
Contributor Author

Shipped in v0.3.0: https://github.com/nubjs/nub/releases/tag/v0.3.0

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.

node not on PATH with nub node install 24 on new install without other node installs

3 participants