← Blog
The Nub Team

Nub 0.11.0

Nub preloads TypeScript 7.1 content mappers, saves type packages on add, installs one copy of a package where pnpm does, and removes the canary channel.

Nub 0.11.0 preloads TypeScript 7.1 content mappers, adds nub add --save-types, installs one copy of a package where pnpm does, and removes the canary channel. It also fixes a broad set of correctness bugs in the runtime, the package manager and nub run.

Nub is an all-in-one toolkit for Node.js written in Rust. The nub command is flag-for-flag compatible with node, while adding full support for TypeScript, JSX, tsconfig.json, .env loading, and modern Web and ECMAScript APIs. It also includes a fast script runner (nub run), package runner (nubx), Node version manager (nub node), and pnpm-compatible package manager.

Content mappers

TypeScript 7.1's content mappers type-check other file types, but TypeScript never runs them. Nub now preloads the ./register export of every package the tsconfig lists, with no --import and no nub.jsonc entry (#1082).

tsconfig.json
{
  // ...
  "contentMappers": [
    { "package": "motherload", "extensions": [".toml", ".yaml"] }
  ]
}
main.ts
import config from "./config.yaml"; // loaded by motherload/register

extends and references are followed, the longest extension answers first, and an entry's options reach its register in CONTENT_MAPPER_OPTIONS.

Type packages on add

nub add --save-types <pkg> also saves the DefinitelyTyped package to devDependencies for each registry package whose installed version ships no types, picking the newest @types release on that package's major (#1110, fixes #1016).

nub add --save-types express

A package with no @types release, or only a deprecated stub, prints skipped @types/<name>: <reason>. An @types entry the project already lists is kept.

Breaking changes

    • The canary channel is gone. nub upgrade --canary and --stable are unknown flags, and install.sh and install.ps1 no longer accept canary. An existing canary install keeps working, and nub upgrade --stable still moves it to a release (#1102)
    • A lockfile missing a required entry is broken. nub ci and nub install --frozen-lockfile exit 16 and name the entry, and a plain nub install repairs it. Both exited 0 and left a tree that could not load (#1105)
    • nub add <pkg>@<tag> saves a prerelease exact, as pnpm does. A stable tag still saves with ^ (#1096)
    • Some lockfiles change once. A local or URL package keeps its peers on its key, and a tree that held duplicate copies of a package collapses them on its next fresh resolve (#1093, #1089)
    • The first install after upgrading relinks once, because the dependency scan's cache was reset (#1113)

Bug fixes

Package manager

PRWhat changed
#1089Duplicate copies. A package installs once where pnpm installs it once. The Nuxt starter went from 17 duplicate copies to 0, and workspace members no longer get two module objects for one package.
#1093Peers of local packages. A file:, URL or git package consumed with two sets of peers loads the right peer after a lockfile install. One entry served both consumers, with no error.
#1081Edges to workspace members. A registry package whose peer a workspace member satisfies links to the member, as pnpm writes it. The install succeeded and require failed with Cannot find module.
#1072, #1109, fixes #1108Local packages from a pnpm lockfile. file: and link: packages read back from nub.lock or pnpm-lock.yaml get the graph a fresh resolve gives. A root peer pinned to a tarball URL no longer fails every resolve after the first with ERR_NUB_BLOCKED_EXOTIC_SUBDEP.
#1111Failed adds. A failed nub add or nub install <pkg> restores package.json, the lockfiles and the workspace yaml. The new dependency stayed in package.json.
#1091, refs #1087--fix-lockfile. nub install --fix-lockfile rewrites the lockfile, keeping its pinned versions. It repairs a package-lock.json written with the dev-optional flags 0.10.0 fixed.
#1112bun.lock local paths. A member's file: directory and a hoisted tarball install under nub ci and --frozen-lockfile, and a bun.lock Nub writes spells local packages as bun does, which bun install --frozen-lockfile accepts.
#1121, fixes #1120Filtered installs. nub install --prod, --dev and --no-optional download only the dependencies they link. Every package in the graph was downloaded into the store.
#1113pnpm-workspace.yaml. A # comment after a pattern or after packages:, and the flow form packages: ['a/*', 'b/*'], are read. Each found no workspace members.
#1113Repeated .npmrc keys. The last script-shell, ignore-scripts or node-mirror:release wins, as in npm. The first won.
#1113packageManager paths. A version that walks out of its package, such as pnpm@../npm/10.9.0, fails to resolve. It installed the other package into the store under the requested name.
#1113nub pm use. nub pm use nub keeps catalog and nohoist from an object-form workspaces, and nub pm use pnpm keeps the other keys under workspaces.
#1113Dependency scan. require('buffer/'), a directory with a main, a template-literal require and import x = require() resolve as Node resolves them, and .d.mts and .d.cts files are not scanned.

Running files

PRWhat changed
#1113Node's permission model. nub --permission --allow-addons app.js starts, --permission in NODE_OPTIONS gets the same --allow-addons message as the flag, and the last of --permission and --no-permission decides, as in Node. The first two failed with ERR_ACCESS_DENIED on Nub's own runtime.
#1113Flags after the entry. nub app.js --permission passes --permission to the program. It was read as Node's own flag.
#1113.env. PASSWORD="foo\$bar" loads foo$bar, and a variable that refers back to itself, such as A=x$A, expands that reference to nothing.
#1113Node version pins. engines.node: "20.11.0 || 22.1.0" matches those exact versions, a v in a range is read as node-semver reads it, and a # comment in .nvmrc or .node-version is read as nvm reads it.
#1113tsx as a loader. --import tsx runs on Node 24.11.1, 25.0 and 25.1. It failed with ERR_METHOD_NOT_IMPLEMENTED.
#1113Native addon. A panic in Nub's addon throws a catchable error. It aborted the process.
#1113nub node install. With Nub's node shim on PATH, it installs only the version asked for.
#1117Home-folder projects. A stray package.json and node_modules in the home folder no longer hang every run under it on macOS. The pre-run dependency scan skips a project rooted at the home folder and gives up after 500 ms.
#1107Windows scripts. Script bodies and lifecycle scripts keep backslashes in environment values, so os.homedir() reads C:\Users\… inside a script and Nub's preload is injected once.

Runtime

PRWhat changed
#1097Node 27 language changes. Intl.Locale#variants is polyfilled and typed in @nubjs/types. Iterator.zip and Iterator.zipKeyed close every input on an early return or a failure. On Node 27, its native versions of these are used. A Test262 subset now runs against the polyfills.
#1113Polyfill shapes. Promise.withResolvers, Iterator.from, URL.parse, the Uint8Array base64 methods and 23 others have no prototype and refuse new, like the builtins. Float16Array and URLPattern are non-enumerable, RegExp.escape escapes lone surrogates, and ErrorEvent before Node 25 reads its fields from prototype getters.
#1099Replaced built-ins. Nub's runtime helpers keep working after application code replaces a global or a prototype method.
#1098--experimental-web-worker. With Node's own Worker global enabled, Nub keeps it and its worker scope instead of installing its polyfill.
#1106, #1113Worker. nub --max-old-space-size=4096 app.mjs no longer makes the global Worker throw ERR_WORKER_INVALID_EXEC_ARGV, and a worker that throws null delivers its error event.
#1101A foreign registerHooks. On Node 22.15 to 24.11, an --import that calls module.registerHooks, such as amaro/strip, no longer makes every CommonJS file throw ERR_INVALID_RETURN_PROPERTY_VALUE.

Scripts and config

PRWhat changed
#1113nub exec -r. A shell-script bin runs in each member's directory. It ran in the invoking directory.
#1113--sequential, --parallel. A regex run outside any workspace runs that package's scripts, as pnpm 9, 10 and 11 do.
#1113Flags before the verb. --cwd=<dir>, --if-present and --reporter-hide-prefix are accepted there. They failed with node: bad option.
#1113PATH. An entry that is not UTF-8 no longer leaves scripts with an empty PATH.
#1113nub config set. Writes through a symlinked nub.jsonc instead of replacing the link.
#1113nub init. Skips git init in a subfolder of a repository.
#1113nubx prompt. Answering Never beside dlx.consent: "prompt" stays off.

Install and upgrade

PRWhat changed
#1102Canary channel. Removed, with nub upgrade --canary and --stable. nub-version: canary in the setup-node action no longer installs.
#1113install.sh. Detects musl through ldd on systems other than Alpine.
#1113setup-node action. Reads .tool-versions and an absolute package.json as a node-version-file, and .nvmrc and .node-version as nvm reads them.

Security

Nub's Rust network clients reject the compromised eMudhra certificate chains (#1019). JavaScript running under Node still relies on Node's own certificate store.

The full release notes list every change in this release.

Get started

View repo