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 test

It 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 test

Running 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

nub run14.7 ms
node --run32.2 ms · 2.2× slower
npm run329.9 ms · 22× slower
pnpm run442.7 ms · 30× slower

View benchmark →

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 — concurrently

Matching 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-allNub
run-p build:js build:cssnub 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=esnext

This matches bun run. The explicit separator still works for muscle memory:

nub run test -- --watch       # identical result; the -- is optional

A 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 script

Lifecycle 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 postbuild

To skip the pre / post hooks (CI, or a security-conscious run), pass --ignore-scripts.

nub run --ignore-scripts build

The 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 set

The 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` from

Inside 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/bash

You 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 -r

Packages 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 package

Multiple --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 only

These 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 topology

Use --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 root

Note 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 parsing

And --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 test

What 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.

  • Runnernubx, the unified runner that resolves a name across every tier.
  • Local bin runnernub exec, a local CLI from node_modules/.bin.
  • Remote bin runnernub dlx, for a tool that isn't installed.
  • Running filesnub <file> for TypeScript and JavaScript execution.
  • Watch mode — restart on file changes.
  • FAQ — monorepo, package-manager, and --node questions answered briefly.