cheat sheet

npm-run-all2

Package-level reference for npm-run-all2 on npm — install, glob-based script matching, run-s vs run-p, and migration from the unmaintained npm-run-all.

npm-run-all2

What it is

npm-run-all2 is the maintained successor to the long-archived npm-run-all package. It runs package.json scripts sequentially (run-s) or in parallel (run-p), with glob-based name matching, cross-platform (Windows/macOS/Linux) consistency, and proper exit-code propagation. It's the orchestration glue inside package.json itself — distinct from concurrently, which is invoked as one-off commands.

The original npm-run-all was unmaintained from 2018 onwards; npm-run-all2 (originally @kazupon/npm-run-all, later forked under its current name) picked it up around 2022 and is now the de-facto choice when projects need glob-style script orchestration.

Install

bash
npm install -D npm-run-all2
pnpm add -D npm-run-all2
yarn add -D npm-run-all2

# One-off without install
npx npm-run-all2 build:*

Output: three binaries on PATH under node_modules/.bin/:

  • npm-run-all — full runner, accepts both --parallel and --sequential flags
  • run-s — shorthand for sequential
  • run-p — shorthand for parallel

(The npm-run-all binary name is intentionally identical to the legacy package — drop-in compatible.)

Versioning & Node support

  • Current major line is 8.x (released 2025) — modernised internals, dropped Node 16.
  • 7.x (2024) is widely deployed and stable.
  • Recent releases require Node 18+.
  • Always a dev dependency; never ships in runtime code.

Package metadata

  • Maintainer: community fork (originally Toru Nagashima for the legacy npm-run-all; the npm-run-all2 lineage is now under the OpenJS-adjacent open-cli-tools / community-fork umbrella).
  • Project home: github.com/bcomnes/npm-run-all2
  • npm: npmjs.com/package/npm-run-all2
  • License: MIT
  • First released: legacy npm-run-all in 2015; npm-run-all2 fork ~2022.
  • Downloads: ~5-8 million per week and rising. Many projects still depend on the original npm-run-all (which kept publishing security patches sporadically) — the fork is the supported choice for new work.

Peer dependencies & extras

npm-run-all2 has a few small dependencies (cross-spawn, memorystream, read-package-json-fast, which) and no peer deps. No companion packages.

Adjacent toolWhen
concurrentlyWhen you need fine-grained prefix control, race-modes, or per-child colours. npm-run-all2's --print-name is more limited.
rimrafThe most common companion — run-s clean build:** invokes rimraf in the clean step
wait-onPair with run-p for "start server in parallel, wait for port, then run tests"
cross-envFor setting env vars cross-platform

Alternatives

ToolTrade-off
concurrentlyBetter UX for parallel watchers — colours, prefixes, race-modes. No glob-script matching.
npm-run-all (legacy)The original. Unmaintained. Use npm-run-all2 for new work.
Bash chaining (&&, &)Free, but breaks on Windows. Many projects ditch it as soon as one Windows contributor joins.
makeCross-platform if make is available. More features (deps, phony targets, parallelism via -j). Outside JS norms.
turbo run / nx run-manyMonorepo task runners — handle topological deps and caching. Overkill for single-package script chains.

Common gotchas

  1. run-p exit code is the worst of all children's exit codes. Unlike concurrently's default (always 0), run-p correctly propagates failure. This is what you want for CI; less surprising than concurrently.
  2. Globs don't match : cross-word. run-s build:* matches build:web and build:api but NOT build:web:bundle. Use ** for arbitrary depth: run-s build:**.
  3. Order of glob matches is alphabetical. run-s "build:*" runs scripts in alphabetical order of their names. If order matters, list explicitly.
  4. Pre/post-hooks (prebuild / postbuild) are respected. run-s build runs prebuildbuildpostbuild. run-p skips them inside the parallel group (each script triggers its own pre/post serially).
  5. -c continues on error. Default is fail-fast; -c (--continue-on-error) makes run-p wait for all children regardless. Useful for lint && test style chains where you want both reports.
  6. --silent is per-runner, not per-script. run-s --silent foo bar suppresses npm-run-all2's logging but children still print.
  7. The legacy npm-run-all and npm-run-all2 install the same binary name (npm-run-all). Don't install both — last-write wins.

Real-world recipes

Parallel script runner with globs

The single most idiomatic use:

json
// package.json
{
  "scripts": {
    "build": "run-s clean build:**",
    "build:client": "vite build",
    "build:server": "tsc -p tsconfig.server.json",
    "build:assets": "node scripts/copy-assets.mjs",
    "clean": "rimraf dist"
  }
}
bash
npm run build

Output:

text
> clean
> rimraf dist
> build:client
> vite v5.0.0 building for production...
> ✓ 234 modules transformed.
> build:server
> tsc -p tsconfig.server.json
> build:assets
> node scripts/copy-assets.mjs

run-s here means sequential — clean fully completes before build steps fire.

Parallel variant — run-p

For independent builds that can race:

json
{
  "scripts": {
    "build": "run-s clean \"build:client && build:server\" build:assets",
    "build:all": "run-p \"build:*\"",
    "build:client": "vite build",
    "build:server": "tsc -p tsconfig.server.json",
    "build:assets": "node scripts/copy-assets.mjs"
  }
}
bash
npm run build:all

Output:

text
> my-app@1.0.0 build:all
> run-p build:client build:server build:assets

> build:client: vite build ... built in 1.2s
> build:server: tsc -p tsconfig.server.json
> build:assets: node scripts/copy-assets.mjs

All three build:* scripts fire simultaneously. run-p waits for all to finish; exit code is non-zero if ANY child failed.

Sequential + parallel composition

npm-run-all (the full binary) accepts -s and -p flags in any order to compose chains:

bash
# Clean first, then build:* in parallel, then deploy
npx npm-run-all -s clean -p "build:**" -s deploy

Output:

text
> clean: rimraf dist
> build:client / build:server (parallel)
> deploy: node scripts/deploy.mjs
json
{
  "scripts": {
    "ship": "npm-run-all -s clean -p \"build:**\" -s deploy"
  }
}

Glob script name matching

run-s build:** matches every depthbuild:web, build:web:bundle, build:server:typecheck. run-s build:* only matches one depth.

json
{
  "scripts": {
    "test": "run-s test:**",
    "test:lint": "eslint .",
    "test:type": "tsc --noEmit",
    "test:unit": "vitest run",
    "test:unit:slow": "vitest run --testTimeout 30000",
    "test:e2e": "playwright test"
  }
}
bash
npm test
# Runs: test:lint → test:type → test:unit → test:unit:slow → test:e2e (alphabetical)

Output:

text
> test:lint: 0 errors
> test:type: ✓ no type errors
> test:unit: 42 passed
> test:e2e: 8 passed

Continue-on-error for lint chains

bash
# Run all linters; report all failures; non-zero exit if any failed
npx run-p --continue-on-error "lint:*"

Output:

text
> lint:js: 2 problems (1 error, 1 warning)
> lint:css: ✓ clean
> lint:md: ✓ clean
ERROR: "lint:js" exited with 1
json
{
  "scripts": {
    "lint": "run-p -c \"lint:*\"",
    "lint:js": "eslint .",
    "lint:css": "stylelint \"**/*.css\"",
    "lint:md": "markdownlint \"**/*.md\""
  }
}

Without -c, the first lint failure stops the others — you only see the first problem per CI run.

Pre/post hooks

run-s respects npm pre/post hooks:

json
{
  "scripts": {
    "build": "run-s build:**",
    "prebuild": "rimraf dist",
    "postbuild": "node scripts/post-build.mjs",
    "build:client": "vite build",
    "build:server": "tsc -p tsconfig.server.json"
  }
}

npm run build triggers prebuildrun-s build:** (→ build:clientbuild:server) → postbuild.

Passing arguments

npm-run-all2 does not have a fully-featured argument forwarding system — children are invoked via npm and inherit nothing from the parent CLI. For per-child args, hard-code them in the script string or use environment variables.

Production deployment

npm-run-all2 is dev-only — same caveat as concurrently. Move to pm2, Docker Compose, or systemd for production multi-process management.

The acceptable production-adjacent use: a release script that chains build, test, version-bump, and publish sequentially. run-s is the right tool here.

Performance tuning

  • run-p is bounded by the slowest child. No way to configure max parallelism (unlike make -j N). For 20+ parallel scripts on a memory-constrained CI runner, split into smaller groups.
  • Pre/post hooks add npm-startup latency (~200ms each). For frequently-run scripts, inline rather than relying on pre/post chains.
  • Glob expansion happens once, up-front. No runtime cost beyond reading package.json.

Version migration guide

From npm-run-all to npm-run-all2

Drop-in:

bash
npm uninstall npm-run-all
npm install -D npm-run-all2

Output:

text
removed 1 package in 1s
added 1 package in 2s

The binary names are identical (npm-run-all, run-s, run-p) — every existing script keeps working. The fork hasn't changed the CLI surface; it's purely a maintenance handoff.

7 → 8

Node 16 dropped. Otherwise transparent.

Security considerations

  1. Children inherit env. Same caveat as concurrently — secrets in process.env flow to every child.
  2. Glob script matching reads package.json. No remote-code-execution risk, but a malicious postinstall somewhere in the dep tree could rewrite package.json scripts; run-s would execute them. Defense in depth.
  3. run-s shells out via npm run. Child scripts execute through the user's shell — same injection caveats as any package.json script.

Configuration patterns

Pre-/post-hook pattern

json
{
  "scripts": {
    "check": "run-s check:**",
    "check:types": "tsc --noEmit",
    "check:lint": "eslint .",
    "check:format": "prettier --check .",
    "check:test": "vitest run"
  }
}

A single npm run check triggers everything alphabetically. CI sets this as a required check.

Watch-mode dev parallel

json
{
  "scripts": {
    "dev": "run-p \"dev:*\"",
    "dev:client": "vite",
    "dev:server": "tsx watch src/server.ts",
    "dev:types": "tsc --watch --noEmit"
  }
}

Reach for concurrently instead when you want named prefixes and colours — npm-run-all2's parallel output interleaves more aggressively.

Troubleshooting common errors

  • "npm WARN config script-shell"npm-run-all2 invokes npm run, which uses the configured shell. On Windows with PowerShell as default shell, some commands need cross-env.
  • "Script not found" — a glob expanded to zero matches. Confirm with npm run (no args) to list all scripts; check spelling.
  • Pre/post hooks not firing in parallel mode — by design. Each parallel child's own pre/post run; the parent run-p itself has no pre/post unless wrapped in another script.
  • Output interleaves badly — switch to concurrently with -n for named prefixes; npm-run-all2 has minimal output customisation.

Ecosystem integrations

  • Most large open-source projects use npm-run-all (legacy) or npm-run-all2 in some scripts. Migration is a one-line lockfile change.
  • Yeoman/Plop generators scaffolding for npm packages commonly include run-s build:** patterns.
  • Lint-staged + husky chains often invoke run-p to run multiple linters on changed files.

When NOT to use this

  • Two-or-fewer scripts in parallel. concurrently "a" "b" is more readable; the glob mechanism doesn't pay off.
  • Need named-prefix output, colours, kill-others-on-success, race modes. concurrently has these; npm-run-all2 does not.
  • Monorepo with topological deps. Use Turborepo or Nx — they handle cross-package ordering and caching that run-s can't.
  • Cross-platform env var setting. Use cross-env; npm-run-all2 does not handle this.
  • Production process management. Use pm2, Docker Compose, systemd — npm-run-all2 has no lifecycle features.

The sweet spot: a package.json with many same-prefix scripts (build:*, test:*, lint:*) that you want to chain in alphabetical order or kick off in parallel — without writing a bash script.

See also