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_BUILDS and exit 1. strictDepBuilds=false restores the warning (#1050 )
A git dependency with a prepare script builds only when approved , and otherwise fails with ERR_NUB_GIT_DEP_PREPARE_NOT_ALLOWED (#1045 )
--filter ./packages selects only a project whose directory is packages . Use ./packages/* or ./packages/** for the projects under it (#1056 )
A --filter that selects no project exits 0 , and --fail-if-no-match exits 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-15 after GitHub retired macos-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 nub condition 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
Full Changelog : v0.9.7...v0.10.0