Undeclared (phantom) dependencies now resolve out of the box, and nubx becomes a single runner for whatever a name points to.
$ nubx build.ts # runs the file
$ nubx test # runs the package.json script
$ nubx vite # runs the local CLI in node_modules/.bin
$ nubx create-vite my-app # fetches and runs a package that isn't installed[!IMPORTANT] Phantom-eject is on for every install — no opt-in and no config change. Existing warm installs re-link once on the next install to converge to the new layout; there is no manual migration step.
Phantom dependencies just work
Nub's isolated install layout used to reject a package's undeclared require/import of a transitive it didn't list — the strict-but-surprising behavior behind a run of real-world breakages.
- Before:
@inkjs/uiimportingreact, or@crawlee/basicrequiring@apify/datastructures, failed with a module-not-found error under the isolated layout even though the package installed fine. - Now: a per-version scanner detects the undeclared use and materializes the ancestor closure that makes it reachable, matching what pnpm's private-hoist fallback does by default. (#302, #319, #321, #328; closes #280)
The detector replaces the previous curated static list — it self-maintains across package versions, and a detection improvement re-scans already-cached content instead of going stale. To bypass the virtual store entirely, install.materialization: disk materializes every dependency to disk.
Unified nubx
One command now runs whatever a name points to. A bare nubx <name> resolves through four tiers and runs the first match:
- File —
nubx build.tsruns the file, the same asnub build.ts. - Script —
nubx testruns thepackage.jsonscript. - Local bin —
nubx viteruns the CLI already innode_modules/.bin. - Registry —
nubx create-vite my-appfetches and runs a package that isn't installed.
Scripts beat bins and a file beats both — the same precedence as nub run. A local match never touches the network.
The registry tier is consent-gated, so nubx never runs remote code silently. On a full local miss in an interactive terminal, nubx prompts before the first fetch of a spec and remembers the answer, so a consented tool doesn't re-prompt; in CI or any non-interactive context it fails closed. -y/--yes is the explicit opt-in for CI and scripts.
nub dlx (short alias nub x) splits off as the explicit downloader — invoking it is the consent, so it fetches without a prompt and runs in CI. nub exec stays the local-bin-only runner. (#224, #275)
Framework support
Scaffold-and-run is verified out of the box — install, dev, build, serve — for Vite, Astro, SvelteKit, SolidStart, Qwik, Next.js, React Router 7 / Remix, Angular, Nuxt 4, and Parcel.
- Vite dev and build serve correctly under the symlinked layout. (#318, closes #315)
- Parcel builds under the global virtual store out of the box —
@parcel/coreno longer splits across two store copies, so the worker farm starts cleanly. (#338) - Expo / React Native installs resolve Babel correctly: an auto-installed wildcard peer now binds the already-resolved major instead of the registry-highest, so react-native is no longer pulled onto an incompatible Babel. (#327)
- Ambient
@types/*resolve under the global virtual store via a pnpm-parity hidden hoist tree. (#293, closes #286)
Runtime
- tsx and ts-node run under Nub on Node 22.15–24.11.0 — a script that shells out to
tsx(for examplenub run dev) previously crashed on that Node range and now runs. (#340) - Import Text —
import contents from "./file.txt" with { type: "text" }, on any extension. (#284) process.versions.nubreports the running Nub version. (#253)- Electron no longer crashes on startup — Nub stopped auto-injecting
--experimental-shadow-realm. (#254, closes #246) Workermirrors more ofnode:worker_threads, and an inbound message event'stargetisself. (#256, #264)- A
NODE_ENVset inside a.envfile is ignored. (#267)
Node provisioning
- Nub auto-provisions Node when a project has no pin and no
nodeonPATH, sonubworks on a machine with no Node installed. (#296, closes #294; #308 covers transientnubxand bin-exec, closes #303) nub node shim/nub node unshiminstall or remove a persistent globalnodeshim. (#297)
The lockfile is now nub.lock
Nub's own lockfile is now nub.lock (previously lock.yaml). An existing lock.yaml keeps working — Nub reads it — and migrates the next time the lockfile actually changes: on a dependency add, remove, or update, Nub writes nub.lock and removes the old lock.yaml in the same diff. A routine install with nothing to change never rewrites or renames the lockfile, and nub ci never touches a checked-in one. (#274)
Install progress
Install progress no longer looks stuck during linking. The old progress bar held its fill at the end of fetching for the whole linking phase, which could read as hung on a slow or network filesystem; the redesigned view drops the bar for a spinner and a fixed-width phase label. (#300)
Three follow-ups refine it: the spinner now animates smoothly (#337); the fetch phase drops its ETA, which printed misleading sub-second estimates on the tail of a few large native binaries (#339); and the progress line now names the package being fetched, updating as the install moves through them (#343).
Package manager
| Change | PR |
|---|---|
nub pm pin locks a project to an exact Nub version | #276 |
| Verify dependency freshness before run, exec, file, and nubx | #278 |
nub ci produces a project-local, copy-safe node_modules | #261 |
prune --prod keeps hoisted production transitives | #250 |
Read .npmrc and dependency-verification settings correctly under a pnpm 11 incumbent | #301, #289 |
A virgin install writes a devEngines caret range and honors a Nub packageManager pin | #266, #265 |
The global settings file moved to nub.jsonc | #291 |
A UTF-8 BOM is stripped before parsing package.json | #269 |
run and exec fixes
node_modules/.binis always on the scriptPATH, even when the directory doesn't exist at spawn. (#283, closes #281)- Files with a dot in the name load correctly. (#245, closes #240 — thanks to @ctorres-lynxlabs)
- Args after the entry point pass through verbatim on the
nodehijack path. (#248) - The cache directory falls back to
%LOCALAPPDATA%on Windows. (#268)
The full release notes list every change in this release.