import { mkdtemp, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, expect, test } from "vitest"; import { execCommand, spawnProcess } from "./index.js"; const roots: string[] = []; afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); async function writeArgumentReader(): Promise { const root = await mkdtemp(path.join(os.tmpdir(), "plugin process arguments ")); roots.push(root); const script = path.join(root, "read arguments.mjs"); await writeFile(script, "process.stdout.write(JSON.stringify(process.argv.slice(2)));\n"); return script; } const argumentsToPreserve = [ "argument with spaces", "--format=%(refname)%09%(committerdate:unix)", "100%", "feature&bugfix", "feature|bugfix", "feature^bugfix", ]; // A bare extensionless command selects the Windows shell; an absolute node.exe path would // skip that path. The real child checks quoting through the public process capabilities. test("execCommand delivers shell arguments to the real child unchanged", async () => { const script = await writeArgumentReader(); const { stdout } = await execCommand("node", [script, ...argumentsToPreserve]); expect(JSON.parse(stdout)).toEqual(argumentsToPreserve); }); test("spawnProcess delivers shell arguments to the real child unchanged", async () => { const script = await writeArgumentReader(); const child = spawnProcess("node", [script, ...argumentsToPreserve], { stdio: "pipe" }); let stdout = ""; child.stdout.setEncoding("utf8"); child.stdout.on("data", (chunk: string) => { stdout += chunk; }); await new Promise((resolve, reject) => { child.once("error", reject); child.once("close", (code) => { if (code === 0) resolve(); else reject(new Error(`Argument reader exited with ${code}`)); }); }); expect(JSON.parse(stdout)).toEqual(argumentsToPreserve); });