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
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--paralleland--sequentialflagsrun-s— shorthand for sequentialrun-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; thenpm-run-all2lineage 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-allin 2015;npm-run-all2fork ~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 tool | When |
|---|---|
concurrently | When you need fine-grained prefix control, race-modes, or per-child colours. npm-run-all2's --print-name is more limited. |
rimraf | The most common companion — run-s clean build:** invokes rimraf in the clean step |
wait-on | Pair with run-p for "start server in parallel, wait for port, then run tests" |
cross-env | For setting env vars cross-platform |
Alternatives
| Tool | Trade-off |
|---|---|
concurrently | Better 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. |
make | Cross-platform if make is available. More features (deps, phony targets, parallelism via -j). Outside JS norms. |
turbo run / nx run-many | Monorepo task runners — handle topological deps and caching. Overkill for single-package script chains. |
Common gotchas
run-pexit code is the worst of all children's exit codes. Unlike concurrently's default (always 0),run-pcorrectly propagates failure. This is what you want for CI; less surprising than concurrently.- Globs don't match
:cross-word.run-s build:*matchesbuild:webandbuild:apibut NOTbuild:web:bundle. Use**for arbitrary depth:run-s build:**. - Order of glob matches is alphabetical.
run-s "build:*"runs scripts in alphabetical order of their names. If order matters, list explicitly. - Pre/post-hooks (
prebuild/postbuild) are respected.run-s buildrunsprebuild→build→postbuild.run-pskips them inside the parallel group (each script triggers its own pre/post serially). -ccontinues on error. Default is fail-fast;-c(--continue-on-error) makesrun-pwait for all children regardless. Useful forlint && teststyle chains where you want both reports.--silentis per-runner, not per-script.run-s --silent foo barsuppresses npm-run-all2's logging but children still print.- The legacy
npm-run-allandnpm-run-all2install 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:
// 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"
}
}
npm run build
Output:
> 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:
{
"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"
}
}
npm run build:all
Output:
> 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:
# Clean first, then build:* in parallel, then deploy
npx npm-run-all -s clean -p "build:**" -s deploy
Output:
> clean: rimraf dist
> build:client / build:server (parallel)
> deploy: node scripts/deploy.mjs
{
"scripts": {
"ship": "npm-run-all -s clean -p \"build:**\" -s deploy"
}
}
Glob script name matching
run-s build:** matches every depth — build:web, build:web:bundle, build:server:typecheck. run-s build:* only matches one depth.
{
"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"
}
}
npm test
# Runs: test:lint → test:type → test:unit → test:unit:slow → test:e2e (alphabetical)
Output:
> test:lint: 0 errors
> test:type: ✓ no type errors
> test:unit: 42 passed
> test:e2e: 8 passed
Continue-on-error for lint chains
# Run all linters; report all failures; non-zero exit if any failed
npx run-p --continue-on-error "lint:*"
Output:
> lint:js: 2 problems (1 error, 1 warning)
> lint:css: ✓ clean
> lint:md: ✓ clean
ERROR: "lint:js" exited with 1
{
"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:
{
"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 prebuild → run-s build:** (→ build:client → build: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-pis bounded by the slowest child. No way to configure max parallelism (unlikemake -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:
npm uninstall npm-run-all
npm install -D npm-run-all2
Output:
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
- Children inherit env. Same caveat as concurrently — secrets in
process.envflow to every child. - Glob script matching reads
package.json. No remote-code-execution risk, but a maliciouspostinstallsomewhere in the dep tree could rewritepackage.jsonscripts;run-swould execute them. Defense in depth. run-sshells out vianpm run. Child scripts execute through the user's shell — same injection caveats as anypackage.jsonscript.
Configuration patterns
Pre-/post-hook pattern
{
"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
{
"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-all2invokesnpm run, which uses the configured shell. On Windows with PowerShell as default shell, some commands needcross-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-pitself has no pre/post unless wrapped in another script. - Output interleaves badly — switch to
concurrentlywith-nfor named prefixes;npm-run-all2has minimal output customisation.
Ecosystem integrations
- Most large open-source projects use
npm-run-all(legacy) ornpm-run-all2in 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-pto 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.
concurrentlyhas these;npm-run-all2does not. - Monorepo with topological deps. Use Turborepo or Nx — they handle cross-package ordering and caching that
run-scan't. - Cross-platform env var setting. Use
cross-env;npm-run-all2does not handle this. - Production process management. Use
pm2, Docker Compose, systemd —npm-run-all2has 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
- Packages: npm-concurrently — sibling parallel runner; richer UX for watchers
- Packages: npm-rimraf — typical first step in a
run-s clean buildchain - Concept: pipes — process orchestration and stream composition