npm Scripts
What are npm scripts?โ
The "scripts" field in package.json lets you define named commands for your project. When you run npm run <name>, npm spawns a shell and executes the corresponding command โ with node_modules/.bin added to the PATH. This means any locally installed CLI tool is available without a global install.
{
"scripts": {
"greet": "echo Hello from npm scripts"
}
}
npm run greet
# Output: Hello from npm scripts
For a broader overview of npm itself, see npm.
Aliz policy โ scripts as the single entry pointโ
All project commands must be defined as npm scripts. No loose shell scripts, Makefiles, or Python scripts for build/dev/test workflows. npm run <name> is the universal interface.
Why this matters:
- Discoverability โ there's one place to look for every command:
package.json. - CI consistency โ CI pipelines call
npm run build, not a custom script buried in a folder. - Onboarding speed โ new team members run
npm run devon day one without hunting for docs.
Cross-platform only. Scripts must work on Windows, macOS, and Linux. Never use bash-specific syntax (rm -rf, cp -r, export VAR=val). Use cross-platform packages instead โ see the Cross-platform scripts section.
Common script namesโ
| Script | Purpose | Typical command |
|---|---|---|
dev | Start dev server | vite |
build | Production build | tsc && vite build |
start | Run production server / preview | node server.js |
test | Run unit tests | vitest run |
test:watch | Run tests in watch mode | vitest |
lint | Run linter | eslint . |
format | Run formatter | prettier --write . |
typecheck | Type-check without emit | tsc --noEmit |
clean | Remove build artifacts | rimraf dist coverage |
preview | Preview production build locally | vite preview |
The scripts start, test, stop, and restart have built-in shorthands โ you can run them without the run keyword:
npm test # same as npm run test
npm start # same as npm run start
Use colon-namespacing to group related scripts: test:watch, test:e2e, lint:fix. This keeps the scripts section scannable and makes intent clear.
Lifecycle scriptsโ
Pre and post hooksโ
npm automatically runs pre<name> before and post<name> after any script:
{
"scripts": {
"prebuild": "rimraf dist",
"build": "vite build",
"postbuild": "echo Build complete"
}
}
Running npm run build executes: prebuild โ build โ postbuild.
npm lifecycle eventsโ
Some scripts run automatically at specific points in the npm lifecycle:
prepareโ runs afternpm installin development. Use it for Husky setup or build steps that must exist after install.prepublishOnlyโ runs beforenpm publish. Use it for final checks or builds before publishing a package.postinstallโ runs after install completes. Use sparingly โ it slows down installs for consumers of your package.
Passing argumentsโ
Use the -- separator to pass arguments through to the underlying command. Everything after -- is appended to the script command.
npm run test -- --coverage
# Executes: vitest run --coverage
npm run lint -- --fix
# Executes: eslint . --fix
Composing scriptsโ
Running in seriesโ
The && operator runs commands sequentially. It works cross-platform in npm scripts because npm spawns a shell that supports it on both Windows and Unix:
{
"scripts": {
"build": "tsc --noEmit && vite build"
}
}
Running in parallelโ
For parallel execution, use a dedicated package:
-
npm-run-all2 โ lightweight, designed for npm scripts.
run-pruns in parallel,run-sruns in series:package.json{
"scripts": {
"validate": "run-p lint typecheck test"
}
} -
concurrently โ better for long-running processes (like dev servers) with labeled, color-coded output:
package.json{
"scripts": {
"dev": "concurrently \"vite\" \"tsc --watch\""
}
}
Keeping scripts shortโ
If a script gets long, break it into sub-scripts and compose them:
{
"scripts": {
"build": "run-s clean typecheck build:vite",
"build:vite": "vite build",
"typecheck": "tsc --noEmit",
"clean": "rimraf dist"
}
}
Prefer composing named sub-scripts over long inline commands. Each script should do one thing and have a clear name.
Cross-platform scriptsโ
Different team members use different operating systems, and CI environments may differ from local machines. Scripts must work everywhere.
| Instead of (bash) | Use (cross-platform) | Package |
|---|---|---|
rm -rf dist | rimraf dist | rimraf |
cp -r src/ dest/ | shx cp -r src/ dest/ | shx |
mkdir -p dir | shx mkdir -p dir | shx |
export NODE_ENV=prod && cmd | cross-env NODE_ENV=prod cmd | cross-env |
source .env && cmd | dotenv -- cmd | dotenv-cli |
Platform-specific commands in npm scripts will be flagged in code review. Always use the cross-platform alternative.
Environment variablesโ
Built-in npm variablesโ
npm exposes metadata about the current package as environment variables prefixed with npm_:
npm_package_nameโ the package name frompackage.jsonnpm_package_versionโ the package versionnpm_lifecycle_eventโ the name of the currently running script
Setting custom variablesโ
Use cross-env for inline variables and dotenv-cli for .env files:
{
"scripts": {
"build:staging": "cross-env VITE_API_URL=https://staging.example.com vite build",
"dev:local": "dotenv -e .env.local -- vite"
}
}
Workspacesโ
In a monorepo with npm workspaces, you can target scripts at specific packages or run them across all workspaces. See npm Workspaces for declaration-order caveats, the full -w targeting semantics, and --include-workspace-root.
Run a script in a specific workspace:
npm run build -w packages/ui
Run a script across all workspaces:
npm run test --workspaces
# or shorthand
npm run test -ws
Use --if-present to skip workspaces that don't define the script:
npm run lint -ws --if-present
Full exampleโ
A well-organized scripts section for a typical Aliz project:
{
"scripts": {
"dev": "vite",
"build": "run-s clean typecheck build:vite",
"build:vite": "vite build",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "playwright test",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "tsc --noEmit",
"clean": "rimraf dist coverage",
"validate": "run-p lint typecheck test",
"prepare": "husky"
}
}
This structure follows all Aliz conventions: colon-namespaced groups, cross-platform commands, composed sub-scripts, and a validate script for pre-push checks.
Resourcesโ
- npm โ overview of npm as a package manager
- Node.js โ JavaScript runtime fundamentals
- npm scripts documentation โ official reference
- npm-run-all2 โ run multiple scripts in series or parallel
- cross-env โ set environment variables cross-platform
- rimraf โ cross-platform
rm -rf - shx โ portable shell commands
- concurrently โ run commands concurrently with labeled output
- dotenv-cli โ load
.envfiles for any command