
























@@ -2,10 +2,38 @@ import { spawn, spawnSync, type SpawnOptions } from "node:child_process";
22import { writeFile } from "node:fs/promises";
33import path from "node:path";
44import { fileURLToPath } from "node:url";
5+import { resolveNpmRunner } from "../../npm-runner.mjs";
6+import { resolvePnpmRunner } from "../../pnpm-runner.mjs";
7+import { buildCmdExeCommandLine } from "../../windows-cmd-helpers.mjs";
58import type { CommandResult, RunOptions } from "./types.ts";
69710export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
81112+type HostCommandInvocation = {
13+args: string[];
14+command: string;
15+env?: NodeJS.ProcessEnv;
16+shell?: boolean;
17+windowsVerbatimArguments?: boolean;
18+};
19+20+type ResolveHostCommandOptions = {
21+comSpec?: string;
22+env?: NodeJS.ProcessEnv;
23+execPath?: string;
24+existsSync?: (path: string) => boolean;
25+platform?: NodeJS.Platform;
26+};
27+28+function hostInvocationFromRunner(runner: HostCommandInvocation): HostCommandInvocation {
29+if (runner.env === undefined) {
30+const invocation = { ...runner };
31+delete invocation.env;
32+return invocation;
33+}
34+return runner;
35+}
36+937export function say(message: string): void {
1038process.stdout.write(`==> ${message}\n`);
1139}
@@ -23,15 +51,81 @@ export function shellQuote(value: string): string {
2351return `'${value.replaceAll("'", `'"'"'`)}'`;
2452}
255354+function portableBasename(value: string): string {
55+return value.split(/[/\\]/u).at(-1) ?? value;
56+}
57+58+function portableExtension(value: string): string {
59+return path.posix.extname(portableBasename(value)).toLowerCase();
60+}
61+62+function isBareCommand(command: string, name: "npm" | "pnpm"): boolean {
63+return portableBasename(command) === command && command.toLowerCase() === name;
64+}
65+66+function resolveEnvValue(env: NodeJS.ProcessEnv, name: string): string | undefined {
67+const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
68+return key === undefined ? undefined : env[key];
69+}
70+71+export function resolveHostCommandInvocation(
72+command: string,
73+args: string[],
74+options: ResolveHostCommandOptions = {},
75+): HostCommandInvocation {
76+const env = options.env ?? process.env;
77+const platform = options.platform ?? process.platform;
78+const comSpec = options.comSpec ?? resolveEnvValue(env, "ComSpec") ?? "cmd.exe";
79+80+if (isBareCommand(command, "pnpm")) {
81+const runner = resolvePnpmRunner({
82+ comSpec,
83+npmExecPath: env.npm_execpath,
84+nodeExecPath: options.execPath ?? process.execPath,
85+ platform,
86+pnpmArgs: args,
87+});
88+return hostInvocationFromRunner(runner);
89+}
90+91+if (isBareCommand(command, "npm")) {
92+const runner = resolveNpmRunner({
93+ comSpec,
94+ env,
95+execPath: options.execPath ?? process.execPath,
96+existsSync: options.existsSync,
97+npmArgs: args,
98+ platform,
99+});
100+return hostInvocationFromRunner(runner);
101+}
102+103+const extension = portableExtension(command);
104+if (platform === "win32" && (extension === ".cmd" || extension === ".bat")) {
105+return {
106+args: ["/d", "/s", "/c", buildCmdExeCommandLine(command, args)],
107+command: comSpec,
108+shell: false,
109+windowsVerbatimArguments: true,
110+};
111+}
112+113+return { args, command, shell: false };
114+}
115+26116export function run(command: string, args: string[], options: RunOptions = {}): CommandResult {
27-const result = spawnSync(command, args, {
117+const env = { ...process.env, ...options.env };
118+const invocation = resolveHostCommandInvocation(command, args, { env });
119+const result = spawnSync(invocation.command, invocation.args, {
28120cwd: options.cwd ?? repoRoot,
29121encoding: "utf8",
30-env: { ...process.env, ...options.env },
122+env: invocation.env ?? env,
31123input: options.input,
32124maxBuffer: 50 * 1024 * 1024,
33125stdio: options.quiet ? ["pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe"],
126+shell: invocation.shell,
34127timeout: options.timeoutMs,
128+windowsVerbatimArguments: invocation.windowsVerbatimArguments,
35129});
3613037131const timedOut = (result.error as NodeJS.ErrnoException | undefined)?.code === "ETIMEDOUT";
@@ -67,10 +161,14 @@ export async function runStreaming(
67161options: RunOptions & { logPath?: string } = {},
68162): Promise<number> {
69163return await new Promise((resolve, reject) => {
70-const child = spawn(command, args, {
164+const env = { ...process.env, ...options.env };
165+const invocation = resolveHostCommandInvocation(command, args, { env });
166+const child = spawn(invocation.command, invocation.args, {
71167cwd: options.cwd ?? repoRoot,
72-env: { ...process.env, ...options.env },
168+env: invocation.env ?? env,
169+shell: invocation.shell,
73170stdio: ["pipe", "pipe", "pipe"],
171+windowsVerbatimArguments: invocation.windowsVerbatimArguments,
74172} satisfies SpawnOptions);
7517376174let log = "";
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。