Nub 0.10.0 brings dependency build scripts and --filter in line with pnpm 12, writes pnpm lockfiles that pnpm accepts in more layouts, and backfills util.debounce, util.throttle and the JSON.parse options proposal.
Important
Five changes can surface in an existing project:
- An unapproved dependency build script fails the install with
ERR_NUB_IGNORED_BUILDSand exit 1.strictDepBuilds=falserestores the warning (#1050) - A git dependency with a
preparescript builds only when approved, and otherwise fails withERR_NUB_GIT_DEP_PREPARE_NOT_ALLOWED(#1045) --filter ./packagesselects only a project whose directory ispackages. Use./packages/*or./packages/**for the projects under it (#1056)- A
--filterthat selects no project exits 0, and--fail-if-no-matchexits 1 (#1064) nub rebuild <pkg>no longer approves the package it names (#1050)
Dependency build scripts
An install that meets a build script nobody has approved now fails, as under pnpm 12. It fails last, once node_modules is linked and the approved builds and the project's own scripts have run. Global installs and nubx stay non-strict.
$ nub install
dependencies:
+ core-js@3.40.0
ERR_NUB_IGNORED_BUILDS
× Ignored build scripts: core-js@3.40.0
help: Run `nub approve-builds` to pick which dependencies may run their
build scripts, or set `strictDepBuilds=false` to install without them.| Area | What changed | Link |
|---|---|---|
| Denials | nub approve-builds '!pkg' and --allow-build='!pkg' record a denial, and --allow-build=pkg refuses a package the project denies. An explicit false outranks dangerouslyAllowAllBuilds.
| #1050 |
| Settings | dangerouslyAllowAllBuilds and strictDepBuilds are read from .npmrc, the environment and, in a pnpm project, pnpm-workspace.yaml.
| #1050 |
nub rebuild
| Reports unapproved packages instead of passing over them. A filtered rebuild works in a shared-lockfile workspace, where it did nothing. | #1050 |
| Member approvals | nub rebuild, nub ignored-builds and nub approve-builds read the same approvals as nub install, workspace members' included.
| #1050 |
| Git dependency approval | Approved by <name>@git+<repository> or the same key with #<commit>. A bare name, name@version or * does not approve one, and a denial installs it unbuilt.
| #1045 |
| Git dependency builds | A hosted dependency such as rimraf@github:isaacs/rimraf#v6.1.3 is now built. It linked with no dist/. The build no longer needs cp, which stock Windows lacks.
| #1045, fixes #885, #886 |
| Project lifecycle stages | Project scripts follow pnpm 12's stage lists in every project: install adds preprepare and postprepare, remove runs the uninstall stages, and --prod skips the prepare family.
| #1052 |
Workspace filters
Nub now uses pnpm 12's selector grammar and directory globs on every workspace command, including nub run (#1056).
- nub run --filter ./packages build
+ nub run --filter './packages/*' build| Area | What changed | Link |
|---|---|---|
| Empty selection | Does nothing and exits 0. install, remove and update print Scope: 0 of N workspace projects, and nub ci still cleans node_modules first, as pnpm's does.
| #1064 |
| Workspace root | run, exec, add and test drop the root from a selection that names no project. Other commands keep it.
| #1056 |
| Repeated filtered install | Reports up to date. It reinstalled and reran lifecycle scripts. | #1055 |
recursiveInstall
| Read from pnpm-workspace.yaml in a pnpm project.
| #1055 |
| Flags before the verb | --filter-prod, -C and --dir work there.
| #1064 |
nub ls in a member
| Lists that member. | #1064 |
Lockfile compatibility
| Area | What changed | Link |
|---|---|---|
| Per-project pnpm lockfiles | Under shared-workspace-lockfile=false, a member's lockfile matches pnpm 10's bytes on the tested workspaces. pnpm's --frozen-lockfile rejected the old form.
| #1060 |
| Frozen installs of those trees | nub install --frozen-lockfile passes on a tree Nub or pnpm 10 wrote, and a plain install keeps a member's locked versions. It failed on the first and re-resolved on the second.
| #1062 |
| Rebuild in those trees | nub rebuild finds a member's packages in the root store. It built nothing and exited 0.
| #1058 |
| pnpm's env document | pnpm-lock.yaml records a pinned pnpm in its leading document, so pnpm 11 and 12 accept Nub's lockfile under --frozen-lockfile, including after nub pm use pnpm.
| #1054 |
Local paths in pnpm-lock.yaml
| link:./x is written link:x and file:./x is written file:x, and empty packages: and snapshots: are omitted. A package spelled with ./ relinks on its first install after upgrading.
| #1079 |
A file: dependency outside the workspace
| A member's file: directory above the workspace root installs under nub ci and --frozen-lockfile. It failed with is not a directory.
| #1004 |
package-lock.json
| An optional dependency of a dev dependency is flagged dev and optional. It was devOptional.
| #1088, fixes #1087 |
| Optional peers | Every resolved optional peer is linked next to its package, a plain install repairs an existing nub.lock, and bun.lock records peers as bun writes them.
| #1040, fixes #937 |
| Catalog updates | nub update -r raises a ^ or ~ catalog entry to the version it resolved, and a catalog-only lockfile change is written instead of skipped.
| #1057 |
Overrides on catalog:
| A range-keyed override matches a catalog: dependency by its reference, as under pnpm.
| #1039, fixes #771 |
Package manager
| Area | What changed | Link |
|---|---|---|
nub pack and nub publish
| Never pack a file from outside the package. A symlinked or absolute main, or a symlinked README, put that file in the tarball.
| #1036 |
workspace: specs
| Both commands resolve workspace: and catalog: specs and apply publishConfig. The specs shipped verbatim, which made the package uninstallable.
| #1036, fixes #817 |
nub update at a workspace root
| Keeps members' locked versions. It re-resolved every member and bumped packages nobody named. | #1043, fixes #1001 |
patchedDependencies
| Git renames, header-only deletions, empty new files and mode changes apply as under pnpm 11. A patched executable keeps its mode. | #1037, fixes #844, #845 |
nub patch-commit
| Replaces the patch file with one consolidated patch instead of appending a section per round. nub patch resolves its target through the lockfile.
| #1010 |
Workspace .npmrc
| A command run in a member reads the root's registry and auth. The member's file layers on top under pnpm 10, Yarn 1 and a Nub project, and is not read under pnpm 11, npm or Bun. | #1051 |
| DNS | Hosts resolve through the OS with an in-process cache, so VPN-scoped DNS, /etc/resolver/* and Windows NRPT apply.
| #953, fixes #942 |
| Broken downloads | A tarball whose connection breaks off mid-body is downloaded again. It failed the install. | #1078 |
| Failed installs | An install that fails after a virtual-store mode change keeps node_modules. It removed the tree before resolving.
| #1035, fixes #841 |
--offline
| Refuses an uncached full packument fetch, which went to the network. | #1059 |
file: dependencies on macOS
| Two projects with a file: dependency at the same relative path get their own files. They shared whichever installed first, and a tarball rebuilt in place stayed stale.
| #995 |
| Overrides to a local copy | A registry package whose dependency is overridden to a file: path is ejected into the project. The link dangled and require failed with Cannot find module.
| #1080 |
| Store locks on NFS | Prune, maintenance and GC lock files open readable, so shared locks work on Linux NFS. They failed with EBADF.
| #1046, fixes #1028 |
| Add-time refusals | --allow-similar-name and --allow-new-name each clear their own check, and every refusal names its flag and allowedUnpopularPackages.
| #1074 |
| Aliases and bins | A dependency under its own name keeps its bin over an alias of the same package, so tsc stays with typescript when typescript5 is also installed.
| #1074 |
| Remix 3 | ^3.0.0, 3.1.0 and >=2 keep the shared store. Only a spec that may select a 3.0.0 prerelease gets a project-local store.
| #1042, fixes #927 |
pnpm 12 through nub pm shim
| The pnpm and pnpx shims run pnpm 12. They failed with SyntaxError: Unexpected identifier 'is'.
| #983 |
Mismatched nub pm shim projects
| Registry and account commands such as npm login pass through to the system manager instead of being refused.
| #1021 |
nub pm use nub
| The Turborepo heads-up says which versions recognize Nub. | #1034 |
| Peer placements | A package with peer-dependent copies reuses its file index and verified store read. Remapping across eight peer contexts went from 1.647 ms to 1.411 ms in a stage benchmark on Linux x86-64. | #984 |
Runtime
| Area | What changed | Link |
|---|---|---|
util.debounce, util.throttle
| Backfilled from Node 26.10 on older supported versions, and typed in @nubjs/types.
| #974 |
JSON.parse options
| JSON.parse(text, { freeze, preferNullPrototype }) is polyfilled from the Stage 2.7 proposal, and typed in @nubjs/types.
| #992 |
| YAML and TOML imports | Values keep NaN, -0, dates and own __proto__ keys. A TOML date is a Date, and a TOML integer past 2^53 is a BigInt. It threw on import.
| #1067 |
UV_THREADPOOL_SIZE
| Assigning it in process.env before the first pool use sizes the pool, as under Node. Nub's preload had already built the pool.
| #1013 |
| Temporal | The polyfilled global has the spec's property attributes. It was a frozen module namespace. | #1015 |
require() of a replaced package
| @js-temporal/polyfill, urlpattern-polyfill and abort-controller load the installed package. They failed with ENOENT on Node 22.15+.
| #1061 |
| Decorated files | 400 decorated ESM files on Node 26.11 start in 0.18s, down from 0.99s, on macOS arm64 with a warm transpile cache. | #796, fixes #795 |
| Startup | Augmented startup no longer loads internal/file on Node 20+.
| #556 |
| Entry tsconfig check | The check follows the entry's real path and skips a dependency's own config. A workspace bin run through its node_modules link failed before starting.
| #1041, fixes #1032 |
| Unreadable tsconfig | The diagnostic names the OS error. It read the same as a missing file. | #899 |
| Varlock | Nub stands down when Varlock already resolved the schema or is the program being launched. varlock run --path ./config exited 1.
| #724 |
| Terminal jobs | A child that stops on its first terminal read resumes after the foreground handoff. It stayed stopped. | #998 |
| Test and watch modes | --test=<value>, --watch=<value>, --watch_path and a watch flag in NODE_OPTIONS stay out of the children they start, which could recurse without end. nodeOptions in nub.jsonc refuses a watch flag.
| #1075, #1086 |
Node discovery and provisioning
| Area | What changed | Link |
|---|---|---|
latest and other aliases
| Aliases and partial pins select releases only. A .nvmrc of latest failed with a 404 once nodejs.org listed a 27.0.0 alpha.
| #1033 |
| Version-manager shims | The version behind a mise, asdf, nodenv or Volta shim is read on every launch. A cached version passed Node 26 flags to Node 24, which exited 9. | #1049 |
| Launch loops | A PATH node that hands the launch back to Nub, or links back to it, is skipped. Any chain of Nub processes stops at 64 with ERR_NUB_LAUNCH_LOOP.
| #1044, #1063, #1073, fixes #832 |
| Windows runtime cache | Validates the cache directory rather than every ancestor. A broad grant on the user's profile made each command print warnings and run without the runtime. | #1047, fixes #908 |
Install and upgrade
| Area | What changed | Link |
|---|---|---|
nub upgrade under mise
| Detects a mise install and runs mise upgrade --bump nub. It took the npm channel, which planted a second global copy.
| #571 |
| Release assets | Each stable release carries packslip.sigstore.json, a signed manifest of its archives, so mise can install Nub straight from the release.
| #982 |
Docs, testing and internals
- Every docs page was restructured and tightened (#994).
- Integration suites run against an offline Verdaccio registry with committed fixtures (#765).
- CI's macOS jobs moved to
macos-15after GitHub retiredmacos-14(4a599b6). - Only the approved release run can write a stable tag (#1017).
What's Changed
- release: only the approved run can write a stable tag by @colinhacks in #1017
- release: the approval summary says to run stager, not stager approve by @colinhacks in #1018
- Resume stopped terminal jobs and isolate the Windows cache gate by @pullfrog[bot] in #998
- pm shim: let registry and account verbs through in a mismatched project by @colinhacks in #1021
- runtime: build libuv's threadpool at the first loop turn, so a program can size its own by @colinhacks in #1013
- aube-linker: interpreter-preference test no longer symlinks /bin/echo by @colinhacks in #1038
- node: resolve latest and other aliases to a release, never a prerelease by @colinhacks in #1033
- tests: node shim check accepts the copy fallback across filesystems by @colinhacks in #1048
- runtime: memoize vendored-package resolution in resolveSpec by @pullfrog[bot] in #796
- pm use nub: say which turbo versions recognize nub by @colinhacks in #1034
- pm: open store lock files readable so shared locks work on NFS by @colinhacks in #1046
- fix(pm): support pnpm 12 shims by @afonsojramos in #983
- resolver: match overrides against a catalog: dependency's reference, as pnpm does by @colinhacks in #1039
- lockfile: rebase only link: versions from a pnpm importer by @Arthur031221 in #1004
- pm: resolve workspace: specs in pack and publish manifests by @colinhacks in #1036
- run: check the tsconfig of the entry's real directory, and skip a dependency's own by @colinhacks in #1041
- pm: resolve DNS through the OS with an in-process cache by @meoyawn in #953
- pm: apply git rename, header-only delete, empty-file and mode patches by @colinhacks in #1037
- pm: eject the store only for the Remix 3.0.0 prereleases by @colinhacks in #1042
- pm: key the macOS extracted-tree cache by package content by @colinhacks in #995
- pm_shim tests: keep an installed Nub shim off the inherited PATH by @colinhacks in #1053
- update: keep members' locked versions when updating at the workspace root by @colinhacks in #1043
- pm: link every resolved optional peer of a package by @colinhacks in #1040
- update: floor-bump catalog entries, and record catalog-only lockfile changes by @colinhacks in #1057
- pm: run pnpm's project lifecycle stages on install, update and remove by @colinhacks in #1052
- node discovery: skip a PATH shim that hands the launch back to nub by @colinhacks in #1044
- runtime: describe the
nubcondition as an export condition, not a runtime key by @colinhacks in #990 - codex: raise hook child descriptor limit by @jdalton in #985
- test: isolate real-app matrix from release-age gate by @pullfrog[bot] in #978
- release: sign a packslip for each stable release by @jdx in #982
- pm: build git deps only when allowlisted, as pnpm 12; prepare without cp; prepare hosted-git archives by @colinhacks in #1045
- runtime cache: validate only the Windows cache directory, not its ancestors by @colinhacks in #1047
- ci: isolate Windows compile fixture caches by @pullfrog[bot] in #1023
- fix(runtime): normalize Temporal namespace property attributes by @afonsojramos in #1015
- runtime: let require() of a clobbered package load the installed package by @colinhacks in #1061
- ci: run the Rust jobs when the published schema or skill.md changes by @pullfrog[bot] in #652
- runtime: polyfill the JSON.parse options proposal (Stage 2.7) by @colinhacks in #992
- test(pm): drop pre-#663 workarounds for the root-manifest cache flake by @colinhacks in #1069
- bench(install): record tool versions in results; fix --cwd; re-measure bun 1.3.14 by @colinhacks in #549
- compile: keep the bootstrap preload when the payload carries a resolve shim by @colinhacks in #1065
- registry: refuse an uncached full packument fetch under --offline by @colinhacks in #1059
- chore(make): add fix target for clippy autofix by @jdalton in #759
- pm: read the workspace root .npmrc from a workspace member by @colinhacks in #1051
- install: keep node_modules when an install fails after a virtual-store mode change by @colinhacks in #1035
- ci: parallelize documentation scans by @jdalton in #1002
- ci(node-matrix): retry setup-node once on the smoke legs by @pullfrog[bot] in #591
- pm: pnpm 12's --filter selector grammar and directory globs for every workspace command by @colinhacks in #1056
- pm: record the pinned pnpm in pnpm-lock.yaml's env document by @colinhacks in #1054
- docs: restructure and tighten every docs page by @colinhacks in #994
- ci: close trunk-red only when each reported workflow recovers; name failing legs in the Node matrix gate by @colinhacks in #588
- test: give the code cache generator a file as stdin by @colinhacks in #1071
- feat(runtime): backfill util debounce and throttle by @jdalton in #974
- rebuild: find a member's packages in the root store under per-project lockfiles by @colinhacks in #1058
- node discovery: never cache a version-manager shim's version by @colinhacks in #1049
- runtime: stop loading internal/file at startup; node-compat: reap each test's session by @colinhacks in #556
- perf(pm): cut eight-context index remapping time by 14.3% by @jdalton in #984
- node discovery: skip a PATH node that links back to nub by @colinhacks in #1063
- pm: a --filter that selects no project exits 0, as pnpm 12 does by @colinhacks in #1064
- pm: patch-commit writes one consolidated patch; nub patch resolves through the lockfile by @btea in #1010
- data loaders: keep NaN, -0, BigInt, dates and proto keys by @colinhacks in #1067
- pm: a repeated filtered install is up to date; recursiveInstall is read from pnpm-workspace.yaml by @colinhacks in #1055
- pm: write per-project lockfiles in their project's frame by @colinhacks in #1060
- compile: report a compiled program's uncaught error where it was thrown by @colinhacks in #1066
- test(registry): offline Verdaccio test registry + Socket safety checks by @jdalton in #765
- env: stand down when Varlock already resolved the schema, or is the program being launched by @colinhacks in #724
- fix(tsconfig): name the OS error when the tsconfig file cannot be read by @colinhacks in #899
- Align dependency build-script policy with pnpm 12 by @colinhacks in #1050
- node: spell --test and --watch bare so the children they start never inherit them by @colinhacks in #1075
- node discovery: bound any chain of nub processes by @colinhacks in #1073
- install: download a tarball again when its connection breaks off mid-body by @colinhacks in #1078
- compile: keep build-machine paths out of region comments by @colinhacks in #1077
- compile: bundle an unbundled package's dependencies from its real directory by @colinhacks in #1068
- feat(upgrade): add mise install channel by @afonsojramos in #571
- tooling: pin chrome-devtools-mcp and launch it on chrome-headless-shell by @colinhacks in #1085
- pm: write pnpm-lock.yaml's local paths and empty sections as pnpm does by @colinhacks in #1079
- pm: name each add-time refusal's own flag; keep a bin with the dependency under its own name by @colinhacks in #1074
- site: bump next to 16.4.5 for Google Fonts /l/font URLs by @colinhacks in #1084
- node: keep watch mode out of its children under every spelling and channel by @colinhacks in #1086
- pm: read member lockfiles before the drift check under per-project lockfiles by @colinhacks in #1062
- pm: flag an optional dep of a dev dep as dev and optional in package-lock.json by @colinhacks in #1088
- pm: eject a registry package whose dependency is overridden to a local copy by @colinhacks in #1080
- compile: ship a package as files when the bundle cannot reach its own install by @colinhacks in #1090
New Contributors
- @Arthur031221 made their first contribution in #1004
- @meoyawn made their first contribution in #953
- @jdx made their first contribution in #982
- @btea made their first contribution in #1010
Full Changelog: v0.9.7...v0.10.0