Skip to content

Commit cde7c14

Browse files
committed
refactor(scripts): 统一 pnpm 命令调用方式
提取 pnpm 命令解析逻辑至共享模块,消除各脚本中重复的平台检测代码。qa.mjs、qa-turbo.mjs 和 qa-benchmark.mjs 现在都通过 buildPnpmCommand 函数获取安全的跨平台调用方式,提高了代码的可维护性和一致性。
1 parent 4c23566 commit cde7c14

4 files changed

Lines changed: 78 additions & 19 deletions

File tree

scripts/pnpm-command.mjs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* 解析当前环境下最稳妥的 pnpm 启动方式。
3+
* npm_execpath 既可能指向 pnpm 的 JS 入口,也可能指向 Windows 的可执行包装器;
4+
* 这里统一返回可直接交给 spawnSync 的命令与前置参数,避免调用方重复判断平台细节。
5+
*
6+
* @returns {{ command: string, argsPrefix: string[] }}
7+
*/
8+
export function resolvePnpmRunner() {
9+
const pnpmExecPath =
10+
typeof process.env.npm_execpath === 'string' ? process.env.npm_execpath : null
11+
const pnpmExecPathLower = pnpmExecPath?.toLowerCase() ?? ''
12+
const pnpmExecPathLooksLikePnpm = pnpmExecPathLower.includes('pnpm')
13+
const pnpmExecPathUsesNodeRuntime = /\.(?:cjs|mjs|js)$/i.test(
14+
pnpmExecPath ?? '',
15+
)
16+
const pnpmExecPathIsDirectlyExecutable = /\.(?:exe|cmd|bat)$/i.test(
17+
pnpmExecPath ?? '',
18+
)
19+
20+
if (pnpmExecPathLooksLikePnpm && pnpmExecPathUsesNodeRuntime) {
21+
return {
22+
command: process.execPath,
23+
argsPrefix: [pnpmExecPath],
24+
}
25+
}
26+
27+
if (pnpmExecPathLooksLikePnpm && pnpmExecPathIsDirectlyExecutable) {
28+
return {
29+
command: pnpmExecPath,
30+
argsPrefix: [],
31+
}
32+
}
33+
34+
return {
35+
command: process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm',
36+
argsPrefix: [],
37+
}
38+
}
39+
40+
/**
41+
* 构造一条可直接传给 spawnSync 的 pnpm 命令。
42+
* 调用方只需要关注业务参数,平台相关的执行细节由共享工具统一吸收。
43+
*
44+
* @param {string[]} pnpmArgs pnpm 原始参数
45+
* @returns {{ command: string, args: string[] }}
46+
*/
47+
export function buildPnpmCommand(pnpmArgs) {
48+
const runner = resolvePnpmRunner()
49+
50+
return {
51+
command: runner.command,
52+
args: [...runner.argsPrefix, ...pnpmArgs],
53+
}
54+
}

scripts/qa-benchmark.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import fs from 'node:fs'
44
import path from 'node:path'
55
import { spawnSync } from 'node:child_process'
6+
import { buildPnpmCommand } from './pnpm-command.mjs'
67

78
const args = process.argv.slice(2)
89
const outputDirArgIndex = args.findIndex((arg) => arg === '--output-dir')
@@ -66,9 +67,14 @@ function stripAnsi(input) {
6667

6768
function runScenario(scenario) {
6869
const [command, commandArgs] = scenario.command
70+
// 报告里保留逻辑命令名便于横向比较,真正执行时再解析为平台安全的 pnpm 调用方式。
71+
const resolvedCommand =
72+
command === 'pnpm'
73+
? buildPnpmCommand(commandArgs)
74+
: { command, args: commandArgs }
6975

7076
const start = process.hrtime.bigint()
71-
const result = spawnSync(command, commandArgs, {
77+
const result = spawnSync(resolvedCommand.command, resolvedCommand.args, {
7278
cwd,
7379
env: baseEnv,
7480
encoding: 'utf8',

scripts/qa-turbo.mjs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env node
22

33
import { spawnSync } from 'node:child_process'
4+
import { buildPnpmCommand } from './pnpm-command.mjs'
45

56
// Turbo 版 QA 编排器。
67
// 这个入口把 workspace QA 拆成可缓存、可并行的任务链,优先服务速度和 affected 场景;
@@ -214,11 +215,14 @@ function hasPathChanges(pathspecs, sinceRef, ignorePatterns = []) {
214215
function resolveTurboRunner() {
215216
// 优先走 `pnpm exec turbo`,确保使用工作区锁定的版本;
216217
// 若外部环境已安装 turbo,则允许回退到全局命令,降低本地接入门槛。
218+
// 这里复用共享 pnpm 解析逻辑,避免 Windows 下把 pnpm 可执行文件交给错误的启动器。
219+
const pnpmTurboCommand = buildPnpmCommand(['exec', 'turbo'])
220+
const pnpmTurboVersionCommand = buildPnpmCommand(['exec', 'turbo', '--version'])
217221
const candidates = [
218222
{
219-
command: 'pnpm',
220-
args: ['exec', 'turbo'],
221-
versionArgs: ['exec', 'turbo', '--version'],
223+
command: pnpmTurboCommand.command,
224+
args: pnpmTurboCommand.args,
225+
versionArgs: pnpmTurboVersionCommand.args,
222226
},
223227
{ command: 'turbo', args: [], versionArgs: ['--version'] },
224228
]
@@ -242,10 +246,11 @@ function runWorkspaceQa(modeValue, sinceRef) {
242246
const turboRunner = resolveTurboRunner()
243247

244248
if (!turboRunner) {
249+
const pnpmTurboVersionCommand = buildPnpmCommand(['exec', 'turbo', '--version'])
245250
throw new CommandFailedError(
246251
'workspace-turbo-not-found',
247-
'pnpm',
248-
['exec', 'turbo', '--version'],
252+
pnpmTurboVersionCommand.command,
253+
pnpmTurboVersionCommand.args,
249254
1,
250255
new Error('turbo command is not available'),
251256
)
@@ -302,7 +307,8 @@ function runRootPwshQa(modeValue, sinceRef) {
302307
// 这样可以在享受 workspace 并行收益的同时,避免一次性重构根目录测试编排。
303308
if (modeValue === 'all') {
304309
console.log('[turbo:qa] run root qa:pwsh (all)')
305-
runCommand('root-qa-pwsh-all', 'pnpm', ['run', 'qa:pwsh'])
310+
const pnpmCommand = buildPnpmCommand(['run', 'qa:pwsh'])
311+
runCommand('root-qa-pwsh-all', pnpmCommand.command, pnpmCommand.args)
306312
return
307313
}
308314

@@ -321,7 +327,8 @@ function runRootPwshQa(modeValue, sinceRef) {
321327
}
322328

323329
console.log('[turbo:qa] run root qa:pwsh (changed)')
324-
runCommand('root-qa-pwsh-changed', 'pnpm', ['run', 'qa:pwsh'])
330+
const pnpmCommand = buildPnpmCommand(['run', 'qa:pwsh'])
331+
runCommand('root-qa-pwsh-changed', pnpmCommand.command, pnpmCommand.args)
325332
}
326333

327334
const sinceRef = mode === 'changed' ? resolveSinceRef() : null

scripts/qa.mjs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { spawnSync } from 'node:child_process'
44
import { existsSync } from 'node:fs'
5+
import { buildPnpmCommand } from './pnpm-command.mjs'
56

67
// 传统版 QA 编排器。
78
// 这个入口复用各 workspace 包自身定义的 `qa` 脚本,不依赖 Turbo 任务图;
@@ -20,10 +21,6 @@ class CommandFailedError extends Error {
2021
}
2122
}
2223

23-
const pnpmExecPath = process.env.npm_execpath
24-
const runPnpmWithNode =
25-
typeof pnpmExecPath === 'string' && pnpmExecPath.toLowerCase().includes('pnpm')
26-
2724
const args = process.argv.slice(2)
2825
let mode = 'changed'
2926
let verbose = false
@@ -105,13 +102,8 @@ function runCommand(step, command, args, options = {}) {
105102
}
106103

107104
function runPnpm(step, pnpmArgs, options = {}) {
108-
if (runPnpmWithNode) {
109-
runCommand(step, process.execPath, [pnpmExecPath, ...pnpmArgs], options)
110-
return
111-
}
112-
113-
const command = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
114-
runCommand(step, command, pnpmArgs, options)
105+
const pnpmCommand = buildPnpmCommand(pnpmArgs)
106+
runCommand(step, pnpmCommand.command, pnpmCommand.args, options)
115107
}
116108

117109
function runCapture(command, args) {

0 commit comments

Comments
 (0)