发布日期: 2025 年 9 月 30 日
cross-env 是一个 Node.js 工具,用于解决不同操作系统间环境变量设置方式不一致的问题,支持 Windows、Linux 和 macOS平台。
package.json
cross-env-10.1.0/package.json
{
"name": "cross-env",
"version": "0.0.0-semantically-released",
"description": "Run scripts that set and use environment variables across platforms",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
"cross-env": "./dist/bin/cross-env.js",
"cross-env-shell": "./dist/bin/cross-env-shell.js"
},
"engines": {
"node": ">=20"
},
"scripts": {
"build": "zshy",
"dev": "zshy --watch",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "tsc --noEmit",
"test": "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"test:e2e": "node e2e/test-cross-env.js && node e2e/test-cross-env-shell.js && node e2e/test-default-values.js",
"validate": "npm run build && npm run typecheck && npm run lint && npm run format:check && npm run test:run"
},
"files": [
"dist"
],
"keywords": [
"cross-environment",
"environment variable",
"windows",
"cross-platform"
],
"author": "Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)",
"license": "MIT",
"dependencies": {
"@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6"
},
"devDependencies": {
"@epic-web/config": "^1.21.1",
"@types/cross-spawn": "^6.0.6",
"@types/node": "^24.1.0",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4",
"eslint": "^9.32.0",
"prettier": "^3.6.2",
"typescript": "^5.8.3",
"vitest": "^3.2.4",
"zshy": "^0.3.0"
},
"repository": {
"type": "git",
"url": "https://github.com/kentcdodds/cross-env.git"
},
"bugs": {
"url": "https://github.com/kentcdodds/cross-env/issues"
},
"homepage": "https://github.com/kentcdodds/cross-env#readme",
"zshy": {
"cjs": false,
"exports": {
".": "./src/index.ts",
"./bin/cross-env": "./src/bin/cross-env.ts",
"./bin/cross-env-shell": "./src/bin/cross-env-shell.ts"
}
},
"prettier": "@epic-web/config/prettier",
"module": "./dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./bin/cross-env": {
"types": "./dist/bin/cross-env.d.ts",
"import": "./dist/bin/cross-env.js"
},
"./bin/cross-env-shell": {
"types": "./dist/bin/cross-env-shell.d.ts",
"import": "./dist/bin/cross-env-shell.js"
}
}
}
cross-env
cross-env-10.1.0/src/bin/cross-env.ts
#!/usr/bin/env node
import { crossEnv } from '../index.js'
// process.argv.slice(2) 用于获取命令行参数(排除掉 node 和脚本路径本身)
crossEnv(process.argv.slice(2))
cross-env-shell
cross-env-10.1.0/src/bin/cross-env-shell.ts
#!/usr/bin/env node
import { crossEnv } from '../index.js'
crossEnv(process.argv.slice(2), { shell: true })
crossEnv
cross-env-10.1.0/src/index.ts
import { spawn } from 'cross-spawn'
function crossEnv(
args: string[],
options: CrossEnvOptions = {},
): ProcessResult | null {
// 1、解析命令行参数
// envSetters:需要设置的环境变量键值对(如 { NODE_ENV: 'production' })
// command:要执行的命令(如 node 或 webpack)
// commandArgs:命令的参数(如 ['server.js'])
const [envSetters, command, commandArgs] = parseCommand(args)
// 2、构建环境变量对象
// 基于当前进程的环境变量(process.env)
// 合并 envSetters 中的自定义环境变量(会经过格式处理)
// 保证跨平台兼容性(如保留 Windows 的 APPDATA 变量)
const env = getEnvVars(envSetters)
// 3、执行命令(当命令存在时)
if (command) {
// 配置子进程启动选项
const spawnOptions: SpawnOptions = {
stdio: 'inherit', // 子进程共享父进程的输入输出流(控制台)
shell: options.shell, // 是否通过 shell 执行命令(可选)
env, // 使用上面构建的环境变量对象
}
// 启动子进程执行命令
// 使用 cross-spawn 的 spawn 函数(跨平台版本的 child_process.spawn)启动子进程
const proc = spawn(
// run `path.normalize` for command(on windows)
// 处理命令名称(如 Windows 路径规范化)
commandConvert(command, env, true),
// by default normalize is `false`, so not run for cmd args
// 处理命令参数
commandArgs.map((arg) => commandConvert(arg, env)),
spawnOptions,
)
// 4、信号处理(进程间通信)
// 当父进程收到终止信号时,将信号传递给子进程
// 确保父进程收到终止信号(如用户按 Ctrl+C)时,子进程能同步终止,避免僵尸进程
process.on('SIGTERM', () => proc.kill('SIGTERM'))
process.on('SIGINT', () => proc.kill('SIGINT'))
process.on('SIGBREAK', () => proc.kill('SIGBREAK'))
process.on('SIGHUP', () => proc.kill('SIGHUP'))
// 5、处理子进程退出
proc.on('exit', (code: number | null, signal?: string) => {
let crossEnvExitCode = code
// 如果退出码为 null(通常是被信号终止),设置默认退出码
// 处理信号终止的情况(如 SIGINT 通常是用户主动中断,返回 0 表示正常退出)
if (crossEnvExitCode === null) {
crossEnvExitCode = signal === 'SIGINT' ? 0 : 1
}
// 父进程使用子进程的退出码退出
process.exit(crossEnvExitCode)
})
return proc
}
return null
}
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test-crossenv": "cross-env NODE_ENV=test node test-crossenv.js"
},
执行 npm run test-crossenv

parseCommand
cross-env-10.1.0/src/index.ts
function parseCommand(
args: string[],
): [Record<string, string>, string | null, string[]] {
// 存储环境变量
const envSetters: Record<string, string> = {}
// 存储命令名称
let command: string | null = null
// 存储命令参数
let commandArgs: string[] = []
// 遍历处理参数
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (!arg) continue // 跳过空参数
const match = envSetterRegex.exec(arg)
// 解析环境变量
if (match && match[1]) {
let value: string
if (typeof match[3] !== 'undefined') {
value = match[3]
} else if (typeof match[4] === 'undefined') {
value = match[5] || ''
} else {
value = match[4]
}
envSetters[match[1]] = value
// 解析命令和命令参数
} else {
// No more env setters, the rest of the line must be the command and args
const cStart = args
.slice(i)
.map((a) => {
const re = /\\\\|(\\)?'|([\\])(?=[$"\\])/g
// Eliminate all matches except for "\'" => "'"
return a.replace(re, (m) => {
if (m === '\\\\') return '\\'
if (m === "\\'") return "'"
return ''
})
})
const parsedCommand = cStart[0]
invariant(parsedCommand, 'Command is required') // 确保命令存在
command = parsedCommand
commandArgs = cStart.slice(1).filter(Boolean) // 过滤空参数
// 退出循环,后续参数已处理
break
}
}
return [envSetters, command, commandArgs]
}
const envSetterRegex = /(\w+)=('(.*)'|"(.*)"|(.*))/
const re = /\\\\|(\\)?'|([\\])(?=[$"\\])/g
getEnvVars
cross-env-10.1.0/src/index.ts
function getEnvVars(
envSetters: Record<string, string>
): NodeJS.ProcessEnv {
// 初始化环境变量对象
const envVars = { ...process.env }
// 特殊处理 Windows 系统的 APPDATA 变量
// APPDATA 是 Windows 系统中存储应用程序数据的目录路径环境变量
// 通常路径为 C:\Users\<用户名>\AppData\Roaming)
if (process.env.APPDATA) {
envVars.APPDATA = process.env.APPDATA
}
// 合并并处理自定义环境变量
Object.keys(envSetters).forEach((varName) => {
const value = envSetters[varName]
if (value !== undefined) {
envVars[varName] = varValueConvert(value, varName)
}
})
return envVars
}
varValueConvert
cross-env-10.1.0/src/variable.ts
function varValueConvert(
originalValue: string,
originalName: string,
): string {
return resolveEnvVars(replaceListDelimiters(originalValue, originalName))
}
replaceListDelimiters
cross-env-10.1.0/src/variable.ts
function replaceListDelimiters(varValue: string, varName = ''): string {
// 1、确定目标分隔符
// Windows 系统的路径列表分隔符是 ;(例如 PATH=C:\a;C:\b)
// 类 Unix 系统(Linux、macOS 等)的路径列表分隔符是 :(例如 PATH=/usr/bin:/bin)
const targetSeparator = isWindows() ? ';' : ':'
// pathLikeEnvVarWhitelist 是一个白名单集合(包含 PATH、NODE_PATH 环境变量名)
if (!pathLikeEnvVarWhitelist.has(varName)) {
return varValue
}
// 匹配一个或多个反斜杠(\\*)后面紧跟一个冒号(:)
// (\\*): 捕获组,匹配0个或多个反斜杠
// 在JavaScript字符串中需要用两个反斜杠表示一个实际的反斜杠
return varValue.replace(
/(\\*):/g,
// 替换回调
// match: 完整的匹配结果(反斜杠序列加冒号)
// backslashes: 捕获组中匹配的反斜杠部分
(match, backslashes) => {
if (backslashes.length % 2) {
// 反斜杠数量为奇数:表示分隔符被转义,移除一个反斜杠
return match.substring(1)
}
// 反斜杠数量为偶数:表示是普通分隔符,替换为目标分隔符
return backslashes + targetSeparator
})
}
const pathLikeEnvVarWhitelist = new Set(['PATH', 'NODE_PATH'])
resolveEnvVars
cross-env-10.1.0/src/variable.ts
function resolveEnvVars(varValue: string): string {
const envUnixRegex = /(\\*)(\$(\w+)|\${(\w+)})/g
return varValue.replace(
envUnixRegex,
// 替换回调函数
// escapeChars:环境变量引用前的所有反斜杠(捕获组 1)
// varNameWithDollarSign:完整的环境变量引用(如 $VAR 或 ${VAR})
// varName:$VAR 格式中的变量名(捕获组 3)
// altVarName:${VAR} 格式中的变量名(捕获组 4)
(_, escapeChars, varNameWithDollarSign, varName, altVarName) => {
// 奇数个反斜杠
// 当反斜杠数量为奇数时,表示这个环境变量引用被转义了,应该保留原始格式(不替换为实际值)
if (escapeChars.length % 2 === 1) {
return varNameWithDollarSign
}
// 偶数个反斜杠
// 反斜杠数量为偶数时,表示是正常的环境变量引用
// 保留一半的反斜杠(因为偶数个反斜杠是成对的转义)
// 拼接上环境变量的实际值(从 process.env 获取,不存在则用空字符串)
return (
escapeChars.substring(0, escapeChars.length / 2) +
(process.env[varName || altVarName] || '')
)
},
)
}
commandConvert
cross-env-10.1.0/src/command.ts
function commandConvert(
command: string,
env: NodeJS.ProcessEnv,
normalize = false,
): string {
// 1、非 Windows 系统直接返回
if (!isWindows()) {
return command
}
// 2、定义正则
// 匹配简单变量引用: $var 或 ${var}
const simpleEnvRegex = /\$(\w+)|\${(\w+)}/g
// 匹配带默认值的 Bash 参数扩展: ${var:-default}
const defaultValueRegex = /\$\{(\w+):-([^}]+)\}/g
let convertedCmd = command
// First, handle bash parameter expansion with default values
// 3、处理带默认值的变量引用
convertedCmd = convertedCmd.replace(
defaultValueRegex,
// 替换回调函数
// match:整个匹配的字符串(如 ${PORT:-3000})
// varName:正则捕获组 1 的值,即环境变量名(如 PORT)
// defaultValue:正则捕获组 2 的值,即默认值(如 3000)
(match, varName, defaultValue) => {
// 优先用环境变量值,否则用默认值
const value = env[varName] || defaultValue
return value
},
)
// 4、处理简单变量引用
convertedCmd = convertedCmd.replace(
simpleEnvRegex,
// 替换回调函数
// match:整个匹配的字符串(如 $PATH 或 ${HOME})
// $1:正则第一个捕获组的值,对应 $VAR 格式中的变量名(如 PATH)
// $2:正则第二个捕获组的值,对应 ${VAR} 格式中的变量名(如 HOME)
(match, $1, $2) => {
// 从捕获组获取变量名($1 对应 $VAR,$2 对应 ${VAR})
const varName = $1 || $2
// 如果环境变量存在,返回 Windows 风格的 %VAR%,否则返回空字符串
return env[varName] ? `%${varName}%` : ''
})
// 5、路径规范化(可选)
return normalize === true ? path.normalize(convertedCmd) : convertedCmd
}
isWindows
function isWindows(): boolean {
return (
// 条件1:检测原生 Windows 系统
process.platform === 'win32' ||
// 条件2:检测 Windows 兼容环境(msys/cygwin)
/^(msys|cygwin)$/.test(process.env.OSTYPE || '')
)
}
process.platform:是 Node.js 内置的进程属性,用于返回当前操作系统的平台标识,不同系统对应固定值:
- Windows 系统(包括 Windows 10/11、Windows Server 等)返回
'win32'(注意:即使是 64 位 Windows,也返回 'win32',这是历史兼容设计);
- macOS 系统返回
'darwin';
- Linux 系统返回
'linux';
process.env.OSTYPE:是环境变量中存储的 “操作系统类型” 标识,常见于 Unix-like 环境或 Windows 兼容层(如 msys、cygwin):
-
msys/cygwin:是 Windows 系统上的两款类 Unix 兼容层工具(可模拟 Linux/macOS 的命令行环境,如 Git Bash 基于 msys),它们会将 OSTYPE 设为 'msys' 或 'cygwin';
- 原生 Linux/macOS 中,
OSTYPE 通常为 'linux-gnu' 或 'darwin',不会匹配此正则。
cross-env 适用场景
- 简单命令执行,不需要 shell 特性。
- 需要高性能执行的场景(不经过 shell 可以略微提高性能)。
- 安全敏感场景(避免 shell 注入风险)。
cross-env-shell 适用场景
- 需要使用 shell 特性(如管道 |、重定向 >、命令组合 && 等)。
- 需要执行复杂的 shell 脚本。
- 需要变量替换、通配符等 shell 功能。