























@@ -1,13 +1,14 @@
11#!/usr/bin/env node
22import { spawn, spawnSync } from "node:child_process";
3-import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3+import { mkdtempSync, readFileSync, rmSync, statSync } from "node:fs";
44import { tmpdir } from "node:os";
5-import { dirname, isAbsolute, relative, resolve } from "node:path";
5+import { delimiter, dirname, extname, isAbsolute, relative, resolve } from "node:path";
66import { fileURLToPath } from "node:url";
7+import { resolvePathEnvKey } from "./windows-cmd-helpers.mjs";
7889const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9-const repoLocal = resolve(repoRoot, "../crabbox/bin/crabbox");
10-const binary = existsSync(repoLocal) ? repoLocal : "crabbox";
10+const repoLocal = resolveCrabboxBinary(process.env, process.platform);
11+const binary = repoLocal ?? resolvePathBinary("crabbox", process.env, process.platform);
1112const args = process.argv.slice(2);
12131314if (args[0] === "--") {
@@ -18,11 +19,95 @@ if (args[userArgStart] === "--") {
1819args.splice(userArgStart, 1);
1920}
202122+function commandCandidates(command, platform) {
23+if (platform !== "win32") {
24+return [command];
25+}
26+if (extname(command)) {
27+return [command];
28+}
29+return [`${command}.exe`, `${command}.cmd`, `${command}.bat`, `${command}.com`, command];
30+}
31+32+function resolveCrabboxBinary(env, platform) {
33+const base = resolve(repoRoot, "../crabbox/bin/crabbox");
34+for (const candidate of commandCandidates(base, platform)) {
35+if (isFile(candidate)) {
36+return candidate;
37+}
38+}
39+return null;
40+}
41+42+function resolvePathBinary(command, env, platform) {
43+if (platform !== "win32") {
44+return command;
45+}
46+for (const candidate of commandCandidates(command, platform)) {
47+if (isFile(candidate)) {
48+return candidate;
49+}
50+}
51+const pathValue = env[resolvePathEnvKey(env)] ?? "";
52+for (const dir of pathValue.split(delimiter).filter(Boolean)) {
53+for (const candidate of commandCandidates(command, platform)) {
54+const fullPath = resolve(dir, candidate);
55+if (isFile(fullPath)) {
56+return fullPath;
57+}
58+}
59+}
60+return command;
61+}
62+63+function isFile(path) {
64+try {
65+return statSync(path).isFile();
66+} catch {
67+return false;
68+}
69+}
70+71+function spawnInvocation(command, commandArgs, env, platform) {
72+const extension = extname(command).toLowerCase();
73+if (platform === "win32" && (extension === ".cmd" || extension === ".bat")) {
74+return {
75+command: env.ComSpec ?? "cmd.exe",
76+args: ["/d", "/s", "/c", buildBatchCommandLine(command, commandArgs)],
77+windowsVerbatimArguments: true,
78+};
79+}
80+return { command, args: commandArgs };
81+}
82+83+const cmdMetaCharactersRe = /([()\][%!^"`<>&|;, *?])/g;
84+85+function escapeBatchCommand(command) {
86+return `${command}`.replace(cmdMetaCharactersRe, "^$1");
87+}
88+89+function escapeBatchArgument(arg) {
90+let escaped = `${arg}`;
91+escaped = escaped.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
92+escaped = escaped.replace(/(?=(\\+?)?)\1$/, "$1$1");
93+escaped = `"${escaped}"`;
94+escaped = escaped.replace(cmdMetaCharactersRe, "^$1");
95+return escaped.replace(cmdMetaCharactersRe, "^$1");
96+}
97+98+function buildBatchCommandLine(command, commandArgs) {
99+const escapedCommand = escapeBatchCommand(command);
100+const escapedArgs = commandArgs.map(escapeBatchArgument);
101+return `"${[escapedCommand, ...escapedArgs].join(" ")}"`;
102+}
103+21104function checkedOutput(command, commandArgs) {
22-const result = spawnSync(command, commandArgs, {
105+const invocation = spawnInvocation(command, commandArgs, process.env, process.platform);
106+const result = spawnSync(invocation.command, invocation.args, {
23107cwd: repoRoot,
24108encoding: "utf8",
25109stdio: ["ignore", "pipe", "pipe"],
110+windowsVerbatimArguments: invocation.windowsVerbatimArguments,
26111});
27112return {
28113status: result.status ?? 1,
@@ -31,10 +116,13 @@ function checkedOutput(command, commandArgs) {
31116}
3211733118function gitOutput(commandArgs) {
34-const result = spawnSync("git", commandArgs, {
119+const gitBinary = resolvePathBinary("git", process.env, process.platform);
120+const invocation = spawnInvocation(gitBinary, commandArgs, process.env, process.platform);
121+const result = spawnSync(invocation.command, invocation.args, {
35122cwd: repoRoot,
36123encoding: "utf8",
37124stdio: ["ignore", "pipe", "pipe"],
125+windowsVerbatimArguments: invocation.windowsVerbatimArguments,
38126});
39127return {
40128status: result.status ?? 1,
@@ -609,10 +697,12 @@ if (
609697}
610698611699const childArgs = childCwd === repoRoot ? args : absolutizeLocalRunPaths(args);
612-const child = spawn(binary, childArgs, {
700+const childInvocation = spawnInvocation(binary, childArgs, childEnv, process.platform);
701+const child = spawn(childInvocation.command, childInvocation.args, {
613702cwd: childCwd,
614703stdio: "inherit",
615704env: childEnv,
705+windowsVerbatimArguments: childInvocation.windowsVerbatimArguments,
616706});
617707618708const signalExitCodes = new Map([
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。