Links#
https://docs.npmjs.com/cli/v11/using-npm/scripts
https://nodejs.org/api/cli.html
https://www.npmjs.com/package/nodemon
https://www.npmjs.com/package/dotenv-cli
https://pm2.io/docs/runtime/reference/ecosystem-file/
https://pm2.io/docs/runtime/features/watch-restart/
https://pm2.keymetrics.io/docs/usage/application-declaration/
1. Your Current Scripts#
{
"local": "node index.js --mode local",
"local:nodemon": "nodemon index.js --mode local",
"local:dev": "dotenv -e .env.local nodemon index.js --mode local",
"test": "node index.js --mode test",
"test:nodemon": "nodemon index.js --mode test",
"dev": "pm2 start index.js --name \"im-server\" --watch -o ../logs/out.log -e ../logs/error.log -- --mode dev",
"dev:nodemon": "nodemon index.js --mode dev",
"prod": "node index.js --mode prod",
"lint": "eslint ."
}
2. Review#
overall#
结论:
可以跑,但不够专业
最大问题不是语法错误,而是职责和命名不清晰
主要问题:
test 被用来启动 test mode app,而不是运行 automated tests
local / local:nodemon / local:dev 重复
dev 直接用 pm2 start 一长串参数,不利于维护
pm2 --watch 不适合做普通 dev script,更不适合 production
log path 写成 ../logs 容易依赖启动目录
--mode 和 NODE_ENV / APP_ENV 的职责需要统一
dotenv 命令最好加 -- 分隔 command
line by line#
| Script |
Review |
local |
可以,但建议命名为 start:local 或 serve:local |
local:nodemon |
可以,但和 local:dev 重复 |
local:dev |
思路对,建议 dotenv -e .env.local -- nodemon ... |
test |
不建议。npm 生态里 test 应该运行测试 |
test:nodemon |
不建议。容易和测试 watcher 混淆 |
dev |
不建议直接塞 PM2 长命令,应该用 ecosystem.config.cjs |
dev:nodemon |
可以,建议改为默认 dev |
prod |
可以跑,但标准名应是 start 或 start:prod |
lint |
正确,但最好再加 lint:fix 和 check |
3. Naming Rule#
recommended script naming:
dev:
local development with watch/restart
start:
production-style start
start:<env>:
start app with a specific env
test:
run automated tests
test:watch:
run tests in watch mode
lint:
run eslint
lint:fix:
run eslint auto-fix
check:
run lint + test
pm2:start:<env>:
start with PM2 ecosystem file
pm2:reload:<env>:
zero-downtime reload when applicable
avoid:
test = start app in test mode
prod = production start only
dev = PM2 process manager start
long PM2 commands inside package.json
4. Environment Rule#
recommended:
NODE_ENV:
Node.js/library runtime mode
usually local / test / production
APP_ENV:
business deployment environment
local / dev / uat / staging / prod
--mode:
optional CLI override
only use if the app really has CLI mode semantics
practical rule:
prefer env variables for deployment
use --mode only for local CLI-like behavior
example:
NODE_ENV=production
APP_ENV=prod
LOG_LEVEL=info
5. Recommended package.json#
Node.js 20+ with native –env-file#
{
"scripts": {
"dev": "node --env-file=.env.local --watch index.js",
"dev:dev": "node --env-file=.env.dev --watch index.js",
"start": "node index.js",
"start:local": "node --env-file=.env.local index.js",
"start:dev": "node --env-file=.env.dev index.js",
"start:test-env": "node --env-file=.env.test index.js",
"start:prod": "node index.js",
"test": "node --test",
"test:watch": "node --test --watch",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"check": "npm run lint && npm test",
"pm2:start:dev": "pm2 start ecosystem.config.cjs --only im-server-dev",
"pm2:start:prod": "pm2 start ecosystem.config.cjs --only im-server-prod",
"pm2:reload:prod": "pm2 reload ecosystem.config.cjs --only im-server-prod --update-env",
"pm2:stop": "pm2 stop im-server-dev im-server-prod"
}
}
notes:
dev:
local watch mode
start:
standard production-style command
start:test-env:
if you really need to start app with test env
do not use test for this
test:
real automated tests
pm2:*:
process manager scripts
use ecosystem file
compatible with dotenv-cli + nodemon#
{
"scripts": {
"dev": "dotenv -e .env.local -- nodemon index.js",
"dev:dev": "dotenv -e .env.dev -- nodemon index.js",
"start": "node index.js",
"start:local": "dotenv -e .env.local -- node index.js",
"start:dev": "dotenv -e .env.dev -- node index.js",
"start:test-env": "dotenv -e .env.test -- node index.js",
"start:prod": "node index.js",
"test": "node --test",
"test:watch": "node --test --watch",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"check": "npm run lint && npm test",
"pm2:start:dev": "pm2 start ecosystem.config.cjs --only im-server-dev",
"pm2:start:prod": "pm2 start ecosystem.config.cjs --only im-server-prod",
"pm2:reload:prod": "pm2 reload ecosystem.config.cjs --only im-server-prod --update-env"
}
}
dotenv-cli:
use -- before the command
clearer parsing when command also has flags
6. nodemon#
install#
npm install -D nodemon dotenv-cli
nodemon.json#
{
"watch": [
"index.js",
"src"
],
"ext": "js,json",
"ignore": [
"node_modules",
"logs",
"coverage",
"dist"
],
"signal": "SIGTERM"
}
why:
avoid watching node_modules / logs
avoid restart loop when app writes logs
keep package.json script short
7. PM2#
why ecosystem file#
PM2 command line is okay for one-off test:
pm2 start index.js --name im-server
professional setup:
ecosystem.config.cjs
app name
env variables
log paths
watch ignore
instances / exec_mode
ecosystem.config.cjs#
module.exports = {
apps: [
{
name: "im-server-dev",
script: "index.js",
exec_mode: "fork",
instances: 1,
watch: ["index.js", "src"],
ignore_watch: ["node_modules", "logs", "coverage"],
out_file: "../logs/im-server-dev-out.log",
error_file: "../logs/im-server-dev-error.log",
merge_logs: true,
env: {
NODE_ENV: "production",
APP_ENV: "dev",
LOG_LEVEL: "debug"
}
},
{
name: "im-server-prod",
script: "index.js",
exec_mode: "cluster",
instances: "max",
watch: false,
out_file: "../logs/im-server-prod-out.log",
error_file: "../logs/im-server-prod-error.log",
merge_logs: true,
env: {
NODE_ENV: "production",
APP_ENV: "prod",
LOG_LEVEL: "info"
}
}
]
};
notes:
dev:
watch can be enabled if this VM is used for development
prod:
watch should usually be false
deploy should restart/reload intentionally
cluster:
only use when app is stateless and safe for multi-process
logs:
for VM deployment only
container/K8S should prefer stdout/stderr
commands#
npm run pm2:start:dev
npm run pm2:start:prod
npm run pm2:reload:prod
pm2 logs im-server-prod
pm2 describe im-server-prod
8. Mode Parsing#
when to use –mode#
--mode 适合:
本地手动切换启动模式
同一个入口执行不同 command behavior
CLI-style app
临时覆盖 APP_ENV
--mode 不适合:
作为生产环境唯一配置来源
替代 NODE_ENV
替代 secret/config injection
每个 npm script 都必须带一个 mode
recommended precedence:
command line --mode
APP_ENV
NODE_ENV
default local
why:
--mode:
manual override
APP_ENV:
deployment environment
NODE_ENV:
runtime/library optimization mode
default:
local developer experience
parse –mode#
function getArgValue(name) {
const prefix = `--${name}=`;
const arg = process.argv.find((item) => item.startsWith(prefix));
if (arg !== undefined) {
return arg.slice(prefix.length);
}
const index = process.argv.indexOf(`--${name}`);
if (index >= 0) {
return process.argv[index + 1];
}
return undefined;
}
export const runtimeMode =
getArgValue("mode") ??
process.env.APP_ENV ??
process.env.NODE_ENV ??
"local";
supported:
node index.js --mode local
node index.js --mode=local
better:
app config reads APP_ENV
--mode only overrides when needed
avoid:
every script depends on --mode
mode and env conflict without clear precedence
validate mode#
const allowedModes = new Set(["local", "dev", "test", "uat", "staging", "prod"]);
if (!allowedModes.has(runtimeMode)) {
console.error(`invalid mode: ${runtimeMode}`);
process.exit(1);
}
why:
fail fast on typo
avoid app silently running with wrong config
bad:
node index.js --mode prdo
app starts with unexpected mode
use mode to load config#
import { config as loadDotenv } from "dotenv";
const mode = runtimeMode;
if (mode === "local") {
loadDotenv({ path: ".env.local" });
}
if (mode === "dev") {
loadDotenv({ path: ".env.dev" });
}
if (mode === "test") {
loadDotenv({ path: ".env.test" });
}
practical rule:
local/dev/test can load .env files
prod should normally use platform-injected environment variables
do not load .env.prod from git
scripts with mode#
{
"scripts": {
"dev": "dotenv -e .env.local -- nodemon index.js --mode local",
"start:local": "dotenv -e .env.local -- node index.js --mode local",
"start:dev": "dotenv -e .env.dev -- node index.js --mode dev",
"start:test-env": "dotenv -e .env.test -- node index.js --mode test",
"start:prod": "node index.js --mode prod"
}
}
note:
this is acceptable if the app genuinely uses --mode
still keep test for automated tests:
"test": "node --test"
Docker#
docker run --rm \
-e NODE_ENV=production \
-e APP_ENV=prod \
order-api:1.0.0
docker run --rm \
-e NODE_ENV=production \
-e APP_ENV=prod \
order-api:1.0.0 \
node index.js --mode prod
recommendation:
Docker/K8S should prefer APP_ENV
--mode in container command is optional
avoid duplicating APP_ENV=prod and --mode dev
Kubernetes#
env:
- name: NODE_ENV
value: production
- name: APP_ENV
value: prod
command:
- node
args:
- index.js
- --mode
- prod
recommendation:
use env for deployment environment
use args only when this service is intentionally CLI-like
PM2#
module.exports = {
apps: [
{
name: "im-server-prod",
script: "index.js",
args: "--mode prod",
env: {
NODE_ENV: "production",
APP_ENV: "prod",
LOG_LEVEL: "info"
}
}
]
};
PM2 rule:
if args has --mode prod, env.APP_ENV should also be prod
do not set args --mode dev with APP_ENV prod
bad vs good#
bad:
"test": "node index.js --mode test"
good:
"test": "node --test"
"start:test-env": "dotenv -e .env.test -- node index.js --mode test"
bad:
APP_ENV=prod node index.js --mode dev
good:
APP_ENV=prod node index.js
bad:
production secrets selected by --mode only
good:
production secrets injected by platform
--mode only selects behavior/config profile
9. Migration From Your Scripts#
old:
local
new:
start:local
old:
local:nodemon / local:dev
new:
dev
old:
test
new:
start:test-env
old:
test:nodemon
new:
dev:test-env or remove
old:
dev with pm2 long command
new:
pm2:start:dev + ecosystem.config.cjs
old:
prod
new:
start / start:prod
10. Recommended Final#
{
"scripts": {
"dev": "dotenv -e .env.local -- nodemon index.js",
"start": "node index.js",
"start:local": "dotenv -e .env.local -- node index.js",
"start:dev": "dotenv -e .env.dev -- node index.js",
"start:test-env": "dotenv -e .env.test -- node index.js",
"start:prod": "node index.js",
"test": "node --test",
"test:watch": "node --test --watch",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"check": "npm run lint && npm test",
"pm2:start:dev": "pm2 start ecosystem.config.cjs --only im-server-dev",
"pm2:start:prod": "pm2 start ecosystem.config.cjs --only im-server-prod",
"pm2:reload:prod": "pm2 reload ecosystem.config.cjs --only im-server-prod --update-env",
"pm2:stop": "pm2 stop im-server-dev im-server-prod"
}
}
dependencies:
npm install express
devDependencies:
npm install -D nodemon dotenv-cli eslint @eslint/js
11. Checklist#
scripts:
dev is local watch
start is normal start
test is automated tests
lint exists
check exists
PM2 commands use ecosystem file
env:
.env.example committed
.env.local ignored
APP_ENV / NODE_ENV precedence documented
production config injected by platform
logs:
VM PM2 logs configured
logrotate configured if writing files
container/K8S uses stdout/stderr