Something went wrong. Try again.
Training wheels for the terminal
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143// End-to-end check of shellQuote: quote hostile words, run them through a real// shell into a program that prints its argv, and assert the program received// exactly the original words. String-level unit tests can't catch shell parsing// quirks (Windows PowerShell 5.1 splitting `-a.b`, dropping `@x`, mangling// embedded quotes), so this is the test that proves quoting actually works.//// Each shell is skipped when it isn't installed.import { describe, it, expect, beforeAll, afterAll } from "vitest";import { spawnSync } from "node:child_process";import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path";import { shellQuote, ShellFamily } from "./commandAssembly";
const WORDS = [ "-c:v", "libx264", "-q:v", "select=not(mod(n\\,90))", "C:\\My Videos\\frames/frame_%04d.png", "C:\\Users\\me\\in.mp4", "/tmp/My Frames/f.png", "it's", 'a"b', 'say "hi there"', "C:\\dir with space\\", 'x\\"y', "@x", "-Dfoo.bar=1", "--flag=value.x", "16,32,64", "=ls", "800x600>", "a|b", "semi;colon", "amp&", "$HOME", "$(whoami)", "`whoami`", "*.mp4", "~/x", "#hash", "{a,b}", "!bang", "black@0.5", "fps=(4*3)/20.5", "drawtext=text='%{pts\\:hms}':x=5",];
function which(cmd: string): boolean { const probe = process.platform === "win32" ? spawnSync("where", [cmd]) : spawnSync("sh", ["-c", `command -v ${cmd}`]); return probe.status === 0;}
// Git Bash on Windows (not WSL's bash.exe, which runs in a different world).const GIT_BASH = "C:\\Program Files\\Git\\bin\\bash.exe";
interface ShellUnderTest { name: string; family: ShellFamily; available: boolean; ext: string; run: (scriptPath: string) => ReturnType<typeof spawnSync>;}
const shells: ShellUnderTest[] = [ { // What the app spawns on Windows (src-tauri/src/lib.rs). name: "Windows PowerShell 5.1", family: "powershell", available: process.platform === "win32" && which("powershell.exe"), ext: ".ps1", run: (p) => spawnSync("powershell.exe", [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", p, ]), }, { name: "bash", family: "posix", available: process.platform === "win32" ? existsSync(GIT_BASH) : which("bash"), ext: ".sh", run: (p) => spawnSync(process.platform === "win32" ? GIT_BASH : "bash", [p], { // Stop MSYS rewriting /tmp/... arguments into Windows paths. env: { ...process.env, MSYS_NO_PATHCONV: "1", MSYS2_ARG_CONV_EXCL: "*" }, }), }, { name: "zsh", family: "posix", available: process.platform !== "win32" && which("zsh"), ext: ".zsh", run: (p) => spawnSync("zsh", ["-f", p]), },];
let dir: string;let argvScript: string;
beforeAll(() => { dir = mkdtempSync(join(tmpdir(), "clc-quote-")); argvScript = join(dir, "argv.js"); writeFileSync( argvScript, "process.stdout.write(JSON.stringify(process.argv.slice(2)));", );});
afterAll(() => { rmSync(dir, { recursive: true, force: true });});
describe.each(shells)("shellQuote through $name", (shell) => { it.skipIf(!shell.available)( "delivers every word to the program unchanged", () => { const node = process.execPath; const quoted = WORDS.map((w) => shellQuote(w, shell.family)).join(" "); const script = shell.family === "powershell" ? `& ${shellQuote(node, "powershell")} ${shellQuote(argvScript, "powershell")} ${quoted}\n` : `${shellQuote(node.replace(/\\/g, "/"), "posix")} ${shellQuote(argvScript.replace(/\\/g, "/"), "posix")} ${quoted}\n`; const scriptPath = join(dir, `run${shell.ext}`); writeFileSync(scriptPath, script);
const result = shell.run(scriptPath); const stdout = result.stdout?.toString() ?? ""; expect(result.stderr?.toString() ?? "").toBe(""); expect(JSON.parse(stdout)).toEqual(WORDS); }, 30_000, );});