
























@@ -0,0 +1,122 @@
1+import { spawn, spawnSync, type SpawnOptions } from "node:child_process";
2+import { writeFile } from "node:fs/promises";
3+import path from "node:path";
4+import { fileURLToPath } from "node:url";
5+import type { CommandResult, RunOptions } from "./types.ts";
6+7+export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
8+9+export function say(message: string): void {
10+process.stdout.write(`==> ${message}\n`);
11+}
12+13+export function warn(message: string): void {
14+process.stderr.write(`warn: ${message}\n`);
15+}
16+17+export function die(message: string): never {
18+process.stderr.write(`error: ${message}\n`);
19+process.exit(1);
20+}
21+22+export function shellQuote(value: string): string {
23+return `'${value.replaceAll("'", `'"'"'`)}'`;
24+}
25+26+export function run(command: string, args: string[], options: RunOptions = {}): CommandResult {
27+const result = spawnSync(command, args, {
28+cwd: options.cwd ?? repoRoot,
29+encoding: "utf8",
30+env: { ...process.env, ...options.env },
31+input: options.input,
32+maxBuffer: 50 * 1024 * 1024,
33+stdio: options.quiet ? ["pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe"],
34+timeout: options.timeoutMs,
35+});
36+37+if (result.error) {
38+throw result.error;
39+}
40+41+const status = result.status ?? (result.signal ? 128 : 1);
42+const commandResult = {
43+stderr: result.stderr ?? "",
44+stdout: result.stdout ?? "",
45+ status,
46+};
47+if (options.check !== false && status !== 0) {
48+if (commandResult.stdout) {
49+process.stdout.write(commandResult.stdout);
50+}
51+if (commandResult.stderr) {
52+process.stderr.write(commandResult.stderr);
53+}
54+die(`command failed (${status}): ${[command, ...args].join(" ")}`);
55+}
56+return commandResult;
57+}
58+59+export function sh(script: string, options: RunOptions = {}): CommandResult {
60+return run("bash", ["-lc", script], options);
61+}
62+63+export async function runStreaming(
64+command: string,
65+args: string[],
66+options: RunOptions & { logPath?: string } = {},
67+): Promise<number> {
68+return await new Promise((resolve, reject) => {
69+const child = spawn(command, args, {
70+cwd: options.cwd ?? repoRoot,
71+env: { ...process.env, ...options.env },
72+stdio: ["pipe", "pipe", "pipe"],
73+} satisfies SpawnOptions);
74+75+let log = "";
76+const append = (chunk: Buffer): void => {
77+const text = chunk.toString("utf8");
78+log += text;
79+if (!options.quiet) {
80+process.stdout.write(text);
81+}
82+};
83+child.stdout?.on("data", append);
84+child.stderr?.on("data", (chunk: Buffer) => {
85+const text = chunk.toString("utf8");
86+log += text;
87+if (!options.quiet) {
88+process.stderr.write(text);
89+}
90+});
91+if (options.input != null) {
92+child.stdin?.end(options.input);
93+} else {
94+child.stdin?.end();
95+}
96+97+let timedOut = false;
98+const timer =
99+options.timeoutMs == null
100+ ? undefined
101+ : setTimeout(() => {
102+timedOut = true;
103+child.kill("SIGTERM");
104+setTimeout(() => child.kill("SIGKILL"), 2_000).unref();
105+}, options.timeoutMs);
106+107+child.on("error", reject);
108+child.on("close", async (code, signal) => {
109+if (timer) {
110+clearTimeout(timer);
111+}
112+if (options.logPath) {
113+await writeFile(options.logPath, log, "utf8");
114+}
115+if (timedOut) {
116+resolve(124);
117+} else {
118+resolve(code ?? (signal ? 128 : 1));
119+}
120+});
121+});
122+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。