Skip to content

Nub 0.9.1

Latest

Choose a tag to compare

@github-actions github-actions released this 12 Sep 23:27
· 9 commits to main since this release

Nub now sizes libuv's threadpool to the cores of the machine on every augmented run, takes a prefix command in nub.jsonc, runs npm ci and npm install on its own engine behind an opt-in shim, and accepts more of the npm lockfiles and projects that npm ci accepts.

Important

Augmented runs on a machine with more than four cores now get a larger libuv threadpool. Nub sets UV_THREADPOOL_SIZE to the larger of four and the cores available to the process (a container gets its cgroup quota; Windows stops at eight). On Linux the threads beyond Node's four run at nice 10, so they yield under contention. Child processes start with Node's default of four. A UV_THREADPOOL_SIZE you set yourself is used as is, and --node / NODE_COMPAT keep Node's default. See Threadpool.

Runtime

Threadpool sized to the machine

Node hands async fs, dns.lookup, async zlib and crypto calls, and native addons such as bcrypt and sharp, to a pool of four threads whatever the core count. Nub now sets the size when it starts Node, on every launcher: the file run, nub run, nubx, nub watch and lifecycle scripts.

nub server.ts                        # pool sized to the cores, 16 on a 16-vCPU box
node server.ts                       # pool of 4
UV_THREADPOOL_SIZE=6 nub server.ts   # your value, used as is

The preload removes Nub's own value from process.env once the pool exists, so a cluster worker or child_process spawn gets Node's default; a child that runs through nub is sized again. On Linux the threads beyond four are reniced to 10 after the pool is built, and on the compat tier (Node 18.19–22.14) a --require sidecar builds the pool so the demoted set is exact. A bcrypt route on a 16-vCPU box went from 18 to 66 requests per second under autocannon at 64 connections, mean of three rounds (benchmark); routes that use the pool lightly are within noise of plain Node. The Threadpool page documents the behavior. (#919)

A prefix command in nub.jsonc

A project can put a command in front of everything Nub runs for it: a file run, a package.json script, and nub watch.

{
  "prefix": "dotenvx run --"   // split like a shell; the array form takes exact arguments
}

A script is wrapped as a whole, so a script that starts no Node process still runs behind the prefix. The program resolves from the project's node_modules/.bin chain, then PATH; a path form anchors to the file that set it. A nested nub run, or a wrapper written in Node, does not wrap the same project again. The nubx, nub dlx and node shim entrypoints do not take it, and compat mode runs without it. The field is in the config reference and the JSON schema. (#915)

AsyncContextFrame on Node 22.9–23.x

Nub passes --experimental-async-context-frame on Node 22.9 through 23.x, the band where the flag exists and does not throw. Node 24 made that AsyncLocalStorage implementation the default; on the 22 line it roughly halves the cost of carrying a store across await, which tracing SDKs and request-context plugins pay on every request. (#912)

Memory-constrained launches

On Linux x64, a direct Node launch inside a cgroup memory budget starts the main isolate with a 16 MiB V8 semi-space on two measured releases: Node 22.23.2 in a 512 MiB–1 GiB budget and Node 24.20.0 at 512 MiB. The flag is hidden from process.execArgv, Workers keep their own resourceLimits, and the tuning stands down when Node options, preloads, NODE_OPTIONS, PnP, a prefix, or an environment loader own startup. Other releases, larger budgets, budgets below 512 MiB, watch mode, and compiled executables keep Node's defaults. (#917)

User loader hooks layered above Nub's

Running tsx script.ts under nub run, or a prepare script that does, failed with require is not defined in ES module scope. Nub's load results now carry responseURL, both tiers step aside when an outer resolve hook labels a .ts file with a bare commonjs/module format, and a require.extensions handler that is already registered stays in charge of its extension. (#920)

Package manager

npm installs on Nub's engine

One opt-in routes the two npm verbs an install pipeline runs through Nub's engine:

nub pm shim --route-installs

With the marker set, a bare npm ci in a project with a package-lock.json runs nub ci, and a bare npm install runs nub install, both from the same lockfile. Everything else reaches the real npm as typed, including an install that names a package or carries a flag Nub does not translate. Lifecycle scripts run as npm runs them, with NODE_ENV=production when dev dependencies are omitted and ignore-scripts honored from .npmrc and the environment. The engine also runs a file: directory dependency's lifecycle scripts in the link target on an npm-lockfile install, as npm does. The --no-route-installs flag turns the routing off. (#925, docs)

npm lockfile compatibility

Frozen-installing popular npm projects under Nub found refusals of lockfiles npm itself wrote.

Area What changed PR
Workspace peers A member's required peer is recorded on its importer, so the freshness check no longer reads every member with a peer as manifest adds <peer> #918
Nested members A member nested inside another member resolves through the parent member's own node_modules before the root #918
Links A link target with no version, as npm writes for a name-only local package, parses #918
Integrity A space-separated multi-digest SRI value verifies when any digest of the strongest algorithm matches, as ssri does #918
Peer placement The peer each npm placement resolves to is recorded as a graph edge. A peer-only package reached solely through a transitive's peer edge was pruned and died at runtime with Cannot find package; a link inside node_modules pointing back at a registry package above it was taken for a local package and never fetched. A lockfile npm wrote for a project with an auto-installed peer now round-trips byte-identical. #923
Manifest sections A dependency declared in more than one section, which npm, pnpm and bun all accept, no longer fails a frozen install with ERR_NUB_OUTDATED_LOCKFILE; each declaration is compared against its own row #928
Patches A bun-style patchedDependencies key such as name@github:owner/repo#sha matches by resolved identity instead of failing ERR_NUB_PATCH_NON_SEMVER_RANGE; pnpm-origin declarations keep pnpm's semver-only grammar #929

Installs and lifecycle scripts

Area What changed PR
tsconfig The PM verbs install past a tsconfig whose extends target is a devDependency the install is about to provide; only a run that executes the program keeps the fatal gate #918
Member scripts A workspace member's lifecycle script gets every ancestor node_modules/.bin on PATH, out to the filesystem root, as npm's run-script does #918
node-gyp Every importer lifecycle script, root and member, gets the lazy node-gyp shim dependency builds already had, so a root preinstall that runs node-gyp install no longer exits 127 #918
Native addons When the project's Node came from Nub's own store, Nub writes the header cache entry node-gyp would have downloaded from nodejs.org, so a from-source build works offline. An entry that already exists is never touched; Windows keeps the download because the official zip ships no headers. #935
Phantom detection electron and vscode are injected by their host process and no install can supply them, so a reference to one is host-provided rather than a phantom. A package such as electron-log stays in the shared store. #939

Note

The phantom scanner version moved with #939, so the first install after upgrading re-scans cached verdicts and re-links a warm tree once.

CLI and documentation

  • The nub agent docs command prints usage and the table of contents instead of the overview page; --list prints the contents alone and --page selects a page (#930).
  • A docs page for the @nubjs/extensions database, with ready-to-use configuration for pnpm and Yarn (5297d2f).
  • The blog post De-phantoming the npm ecosystem, a daily scan of 10,000 high-download packages published as that database (#926, 7319aa6, d077198, 8375a03, 11a435e, f231db0, b0a711b, afa009f).
  • A Threadpool runtime page, an AsyncLocalStorage entry on the modern APIs page, and a memory-constrained launches section on the runtime overview (#919, #912, #917).
  • The config reference shows an absolute nodeExecutable path, with the Windows spelling (5da9a60).
  • The GitHub Action page shows setup-nub beside a kept actions/setup-node step (12c2854).
  • The Nub 0.9 release post (56175e8), and a blog post's right-gutter table of contents now shows from the lg breakpoint (0ede70a).

Testing & internals

  • An npm-incumbent corpus: 28 popular npm projects frozen-installed under Nub in CI and checked against their own lockfile, which is where the #918 and #923 findings came from (#922, ac4fa4f).
  • The cross-runtime scoring leaves a test the reference Node itself skips out of the node-relative denominator, for every runtime; the official build skips all 245 quic/ tests. The homepage and README figures are regenerated: Nub passes 98.4% of the 4,690 tests Node 26.7 runs and passes (#931).
  • A Windows dispatch benchmark times a nub call from cmd.exe through npm's nub.cmd shim against the hardlinked nub.exe, with its first saved run (e5fa958, 1b8e7ef).
  • A runtime benchmark family under tests/bench/runtime/: threadpool, AsyncContextFrame, pool-bound routes (bcrypt, scrypt, pbkdf2, sharp, gzip, brotli, RSA keygen) and pool-priority beside busy co-tenants, with saved runs on 8, 16 and 22 vCPU (9134538, 96f7a14, a33bc4c, b058f75, 746c47c, e88ce4f, ab890a2, d4d9e8f, 92c348e, 250372c, d3d8a38, 78a0225).
  • A chart generator for the benchmark figures, with its margin and legend rules (c05a354, 056fa95, a71d9e3, 2e18646, 0e10604, 5a8a8b5, 9654c1b).
  • The release workflow holds publish-npm open until every one of the ten packages answers a registry read, so the Docker and install jobs no longer race a package the registry has not served yet (3f87d09).
  • Pull-request CI is opt-in behind the ci label, and ci-watch requires the CI gate aggregate before a green verdict (5c0b233, 291af85).
  • The docs bake key is renamed __NUB_DOCS_KEY so it counts as internal plumbing under the brand-boundary test (#921).
  • The governed rustc wrapper keeps stdin, so a build-script feature probe fails on stable as it should (fa34f36).
  • The download-stats snapshot folds paginated release pages before parsing (c25bc82, 2b9a448).
  • Local Lat search is wired into the agent workflows (fd626cc).
  • Cold-start research notes: the entropy change landed in Node as nodejs/node#65796, and --use-system-ca is not free on a populated macOS keychain (88a0167, d3bf766).

What's Changed

  • runtime: enable AsyncContextFrame for AsyncLocalStorage on Node 22.9–23.x by @colinhacks in #912
  • config: a prefix field that wraps file runs, scripts, and watch by @colinhacks in #915
  • build: rename the docs bake key to __NUB_DOCS_KEY by @colinhacks in #921
  • pm: accept the npm lockfiles and projects that npm ci accepts by @colinhacks in #918
  • runtime: let a user hook layered above nub's keep ownership of its files by @colinhacks in #920
  • tests: an npm-incumbent corpus, frozen-installed under nub and checked against its lockfile by @colinhacks in #922
  • pm: record the peer each npm lockfile placement resolves to as a graph edge by @colinhacks in #923
  • pm: accept a dependency declared in more than one manifest section by @colinhacks in #928
  • pm: run npm ci and npm install on nub's engine behind nub pm shim --route-installs by @colinhacks in #925
  • pm: accept a bun-style source identity as a patchedDependencies key by @colinhacks in #929
  • agent: show concise docs help and table of contents by @colinhacks in #930
  • tests: leave the tests the reference node skips out of the node-relative denominator by @colinhacks in #931
  • De-phantoming the npm ecosystem by @colinhacks in #926
  • runtime: size libuv's threadpool to the cores on augmented runs by @colinhacks in #919
  • compile: precompile ESM and reuse published extractions by @colinhacks in #932
  • pm: fill node-gyp's header cache from the provisioned Node by @colinhacks in #935
  • Tune the main nursery for memory-constrained Node launches by @colinhacks in #917
  • phantom: treat host-provided modules as declared by @colinhacks in #939

Full Changelog: v0.9.0...v0.9.1