


























@@ -7,6 +7,7 @@ import { pathToFileURL } from "node:url";
7788type Options = {
99altScreen: boolean;
10+help: boolean;
1011mirrorPath: string;
1112mode: "fake" | "local" | "all";
1213vitestArgs: string[];
@@ -26,6 +27,12 @@ const CHILD_SIGTERM_GRACE_MS = 500;
2627const CHILD_SIGKILL_GRACE_MS = 5_000;
2728const MIRROR_READ_CHUNK_BYTES = 1024 * 1024;
2829const CHILD_OUTPUT_TAIL_BYTES = 128 * 1024;
30+const BOOLEAN_OPTIONS = new Set(["--help", "-h", "--no-alt-screen"]);
31+const VALUE_OPTIONS = new Set(["--mode", "--mirror-path"]);
32+33+class CliArgumentError extends Error {
34+override name = "CliArgumentError";
35+}
29363037type KillableChild = {
3138pid?: number;
@@ -48,28 +55,58 @@ function readOption(args: string[], name: string): string | undefined {
4855if (idx < 0) {
4956return undefined;
5057}
51-return args[idx + 1]?.trim() || undefined;
58+const value = args[idx + 1];
59+if (!value || value.startsWith("--")) {
60+throw new CliArgumentError(`${name} requires a value`);
61+}
62+return value.trim();
5263}
53645465function readMode(args: string[]): Options["mode"] {
5566const mode = readOption(args, "--mode") ?? "fake";
5667if (mode === "fake" || mode === "local" || mode === "all") {
5768return mode;
5869}
59-throw new Error(`--mode must be fake, local, or all; got ${JSON.stringify(mode)}`);
70+throw new CliArgumentError(`--mode must be fake, local, or all; got ${JSON.stringify(mode)}`);
71+}
72+73+function usage(): string {
74+return [
75+"Usage: node --import tsx scripts/dev/tui-pty-test-watch.ts [options] [-- vitest args...]",
76+"",
77+"Options:",
78+" --mode <fake|local|all> Select TUI PTY test group (default: fake)",
79+" --mirror-path <path> Write/read mirrored ANSI output at this path",
80+" --no-alt-screen Print without switching to the terminal alt screen",
81+" -h, --help Show this help",
82+].join("\n");
83+}
84+85+function validateOwnArgs(args: string[]): void {
86+for (let idx = 0; idx < args.length; idx += 1) {
87+const arg = args[idx] ?? "";
88+if (BOOLEAN_OPTIONS.has(arg)) {
89+continue;
90+}
91+if (VALUE_OPTIONS.has(arg)) {
92+idx += 1;
93+continue;
94+}
95+throw new CliArgumentError(`Unknown argument: ${arg}`);
96+}
6097}
61986299function parseOptions(args = process.argv.slice(2)): Options {
63100const separator = args.indexOf("--");
64101const ownArgs = separator >= 0 ? args.slice(0, separator) : args;
65102const vitestArgs = separator >= 0 ? args.slice(separator + 1) : [];
66-const mirrorPath =
67-readOption(ownArgs, "--mirror-path") !== undefined
68- ? path.resolve(readOption(ownArgs, "--mirror-path") ?? "")
69- : DEFAULT_MIRROR_PATH;
103+validateOwnArgs(ownArgs);
104+const mirrorPathOption = readOption(ownArgs, "--mirror-path");
70105return {
71106altScreen: !ownArgs.includes("--no-alt-screen"),
72- mirrorPath,
107+help: ownArgs.includes("--help") || ownArgs.includes("-h"),
108+mirrorPath:
109+mirrorPathOption !== undefined ? path.resolve(mirrorPathOption) : DEFAULT_MIRROR_PATH,
73110mode: readMode(ownArgs),
74111 vitestArgs,
75112};
@@ -209,6 +246,10 @@ async function drainNewMirrorData(
209246210247async function main(): Promise<void> {
211248const options = parseOptions();
249+if (options.help) {
250+process.stdout.write(`${usage()}\n`);
251+return;
252+}
212253const useAltScreen = shouldUseAltScreen(options);
213254await createMirrorFile(options.mirrorPath);
214255@@ -422,6 +463,10 @@ async function main(): Promise<void> {
422463423464if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
424465main().catch((error: unknown) => {
466+if (error instanceof CliArgumentError) {
467+process.stderr.write(`${error.message}\n`);
468+process.exit(1);
469+}
425470process.stderr.write(
426471`${error instanceof Error ? error.stack || error.message : String(error)}\n`,
427472);
@@ -433,6 +478,8 @@ export const testing = {
433478 appendBufferTail,
434479 createChildStopper,
435480 drainNewMirrorData,
481+ parseOptions,
436482 readNewMirrorData,
437483 signalChildProcessTree,
484+ usage,
438485};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。