Script runner
A drop-in for pnpm run and npm run — every flag carries over with the same spelling and semantics, 24× faster on the cold path, with the full workspace and lifecycle-hook surface preserved.
A drop-in for pnpm run and npm run, nub run carries every flag over with the same spelling and semantics, down to the recursive and workspace-filter ones a real monorepo leans on. Existing invocations work unchanged:
# resume an interrupted recursive run mid-graph
nub run -r --resume-from @scope/pkg build
# machine-readable per-package results for CI
nub run -r --reporter=ndjson build
nub run -r --stream --reporter-hide-prefix build
nub run -r --workspace-concurrency 4 build
# skip members without the script
nub run -r --if-present testIt reads your package.json directly — 24× faster on the cold path — and package management stays with whatever you already use (pnpm, npm, yarn, bun). This page is a cookbook: each section is a task with the exact command to run.
A few flags behave differently. The --report-summary flag streams results live via --reporter=ndjson instead of writing a static file. The --if-present flag is workspace-scoped: it skips members missing the script during a recursive run. The legacy npm knobs --scripts-prepend-node-path, --unsafe-perm, and --npm-path are not needed and not accepted.
nub run <script>
Pass the script name after run. The name must match a key in your package.json's "scripts" field.
nub run build
nub run dev
nub run testRunning a script is always nub run <script> — barewords like nub dev do not fall through to scripts. (If you type nub build and build is a real script, Nub's error tells you to type nub run build.) See Running files for how nub <file> works.
Dispatch is handled in Rust with no Node bootstrap in the wrapper, so the runner adds only a few milliseconds before your script's first byte — faster than Node's own node --run:
script dispatch · warm · 50 runs
Run several scripts at once
A /regex/ selector in place of a script name runs every script whose name matches, mirroring pnpm run. This is the in-package equivalent of npm-run-all's run-p — without the extra dependency or the per-task PM re-spawn, since Nub spawns each script body directly.
nub run "/^build:/" # runs build:js, build:css, build:types — concurrentlyMatching scripts run concurrently by default, capped at min(4, CPU count), with each line prefixed by its script name. To run them one at a time instead (the run-s behavior), cap the concurrency at one:
# one at a time, in package.json order
nub run --workspace-concurrency 1 "/^build:/"The selector must be a slash-delimited regular expression literal (/^build:/); a plain build:* is treated as a literal script name, not a glob. Regex flags are not supported. An exact script name (no slashes) always runs that single script unchanged.
Coming from npm-run-all / npm-run-all2:
| npm-run-all | Nub |
|---|---|
run-p build:js build:css | nub run "/^build:(js|css)$/" |
run-p "build:*" | nub run "/^build:/" |
run-s "build:*" | nub run --workspace-concurrency 1 "/^build:/" |
Selecting an arbitrary, unrelated set of scripts (for example build, lint, test together, which share no name pattern) is not yet supported — track #102.
Forward arguments
Trailing arguments after the script name are passed straight through to the script. You do not need the -- separator that npm run requires.
nub run test --watch # forwards --watch to the test script
nub run build --target=esnext # forwards --target=esnextThis matches bun run. The explicit separator still works for muscle memory:
nub run test -- --watch # identical result; the -- is optionalA flag before the script name (nub run --filter core test) is Nub's; a flag after the script name (nub run test --filter) is the script's. So Nub-side flags always go before <script>:
nub run --silent build # --silent is Nub's (suppresses the preamble)
nub run build --silent # --silent is forwarded to the build scriptLifecycle hooks
Nub runs pre<script> and post<script> hooks automatically, matching npm run semantics. Given these scripts:
{
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc -p .",
"postbuild": "cp README.md dist/"
}
}A single nub run build runs all three in order:
nub run build # runs prebuild, then build, then postbuildTo skip the pre / post hooks (CI, or a security-conscious run), pass --ignore-scripts.
nub run --ignore-scripts buildThe npm_* environment
Nub populates the child process with the full npm-compatible environment, so scripts and tooling that read npm_* variables behave exactly as they do under npm run. Locally installed CLIs are on PATH via the node_modules/.bin chain, so you can call them by bare name.
{
"scripts": {
"build": "tsc && echo \"built $npm_package_name@$npm_package_version\""
}
}nub run build # tsc resolves from node_modules/.bin; npm_* vars are setThe injected set includes:
npm_lifecycle_event
npm_lifecycle_script
npm_execpath
npm_node_execpath
npm_command
npm_config_user_agent
npm_package_name
npm_package_version
npm_package_json
npm_package_config_* # per-package config keys from package.json#config
npm_config_registry # the resolved registry from the .npmrc chain
npm_config_node_gyp # path to a runnable node-gyp, for native builds
INIT_CWD # the directory you invoked `nub run` fromInside a script, spawning node (by shebang or child_process.spawn("node", …)) re-enters Nub through the PATH shim, so child processes stay transpiled and augmented — see --node below.
--script-shell
If your .npmrc sets script-shell, Nub uses it to invoke script bodies. The rest of .npmrc (registry, auth, node-linker, hoist-pattern) is package-manager territory and is not read here.
# .npmrc
script-shell = /bin/bashYou can also override the shell per invocation:
nub run --script-shell /bin/bash build-r, --recursive
In a monorepo, -r (or --recursive) runs the script in every workspace package. Nub reads workspace topology from package.json's "workspaces" field (npm / Yarn / Bun) or pnpm-workspace.yaml (pnpm).
nub run -r build # run "build" in every workspace package
nub run --recursive test # --recursive is the long form
nub run --workspaces lint # --workspaces is the npm-style alias for -rPackages run in topological order by default: a package's dependencies build before it does. Workspace discovery walks up from your current directory to find the workspace root, so these commands work from anywhere inside the monorepo.
--filter
The --filter flag (or -F) takes pnpm's filter grammar verbatim. Select by exact name, scope glob, name wildcard, or path glob.
nub run --filter @org/api dev # one package by name
nub run --filter "@org/*" build # every package in a scope
nub run --filter "*-utils" test # name wildcard
nub run --filter "./packages/*" lint # path glob
nub run --filter '!@org/legacy' build # exclude a packageMultiple --filter flags compose as a union of the matched sets:
nub run --filter @org/api --filter @org/web build--filter "@org/web..." — graph selectors: the dependency-graph filter forms walk the workspace package graph. Use the ... syntax to pull in related packages.
nub run --filter "@org/web..." build # @org/web + dependencies (downstream)
nub run --filter "...@org/web" build # @org/web + dependents (upstream)
nub run --filter "@org/web^..." build # @org/web's dependencies only
nub run --filter "...^@org/web" build # @org/web's dependents onlyThese are exactly the forms pnpm --filter supports, so existing monorepo muscle memory carries over directly.
--filter "[ref]" — changed since: the [ref] filter form selects packages with changes since a git ref — the common CI pattern for testing only what moved.
nub run --filter "[main]" test # packages changed since main
nub run --filter "[HEAD~1]" build # packages changed since the last commit
# changed packages AND their dependents
nub run --filter "...[origin/main]" test--parallel, --sequential
Across a workspace, Nub runs packages concurrently up to a cap (CPU count by default) while respecting topological order. Tune or override that.
nub run -r --workspace-concurrency 4 build # cap at 4 concurrent scripts
nub run -r --parallel dev # all at once, no topo order or cap
nub run -r --sequential migrate # one at a time, ignore topologyUse --parallel for long-running dev servers where there is nothing to order, and --sequential for scripts that must not overlap.
--no-bail
By default a workspace run bails on the first package that fails (--bail). To run every package regardless and report failures at the end, use --no-bail.
nub run -r test # default: stop at the first failing package
nub run -r --no-bail test # run every package, report failures at the end--resume-from
When a long topological run dies partway through, --resume-from skips the topological predecessors of a package so you restart where it broke instead of from the top.
nub run -r --resume-from @org/api build-w, --workspace-root
The -w flag (pnpm's --workspace-root) targets only the root project, regardless of where you invoke it from. To run recursively across members and also include the root package, use the npm-style --include-workspace-root.
nub run -w lint # run "lint" at the workspace root only
nub run -r --include-workspace-root lint # all members PLUS the rootNote that -w is pnpm's boolean root selector, not npm's member selector. To select a member the npm way, use the long --workspace <name> (repeatable) or --filter <name>.
nub run --workspace @org/api --workspace @org/web build--stream, --aggregate-output
On a TTY, Nub streams interleaved, per-package-prefixed output (<pkg> | <line>, matching pnpm -r). On CI / non-TTY it buffers per package and flushes on finish. Override either default explicitly.
nub run -r --stream build # force interleaved live output
nub run -r --aggregate-output build # force per-package buffering
nub run -r --reporter ndjson build # one JSON event per line, for CI parsingAnd --silent (or -s) suppresses Nub's $ <command> preamble (it does not suppress the script's own stdout):
nub run --silent build--node
By default, the nub executable is aliased as node for the duration of a nub run call, so any subprocess that spawns node (a script shebang, child_process.spawn("node", …)) gets Nub's augmentation too. Pass --node to turn that off for one invocation — see the runtime overview for the full contract.
nub run --node testWhat still happens under --node: script lookup, workspace walk-up, --filter evaluation, npm_* env injection, node_modules/.bin on PATH, and the pre / post lifecycle hooks. Reach for it when a script's #!/usr/bin/env node shebang chain expects plain Node, when bisecting a Nub bug, or when CI wants byte-exact Node runtime behavior with Nub's workspace selection.
Related
- Runner —
nubx, the unified runner that resolves a name across every tier. - Local bin runner —
nub exec, a local CLI fromnode_modules/.bin. - Remote bin runner —
nub dlx, for a tool that isn't installed. - Running files —
nub <file>for TypeScript and JavaScript execution. - Watch mode — restart on file changes.
- FAQ — monorepo, package-manager, and
--nodequestions answered briefly.
Runner
One command to run whatever a name points to — a file, a script, a local CLI, or a package off the registry. Local resolution first, and a registry fetch only with your consent, never silently in CI.
Local bin runnernub exec
Run a CLI installed in the project by name, straight from node_modules — an order of magnitude faster than npx, and never reaching the registry.