parse-args.ts

 1export interface ParsedArgs {
 2  command?: string;
 3  options: Record<string, string | boolean>;
 4  positional: string[];
 5}
 6
 7export function parseArgs(args: string[]): ParsedArgs {
 8  const [, , command, ...rest] = args;
 9  const options: Record<string, string | boolean> = {};
10  const positional: string[] = [];
11
12  for (let i = 0; i < rest.length; i += 1) {
13    const arg = rest[i];
14    if (!arg) continue;
15
16    if (arg.startsWith("--")) {
17      const eqIndex = arg.indexOf("=", 2);
18      let key: string;
19      let value: string | undefined;
20      if (eqIndex !== -1) {
21        key = arg.slice(2, eqIndex);
22        value = arg.slice(eqIndex + 1);
23      } else {
24        key = arg.slice(2);
25      }
26      if (!key) continue;
27      if (value !== undefined) {
28        options[key] = value;
29      } else if (rest[i + 1] && !rest[i + 1]?.startsWith("-")) {
30        options[key] = rest[i + 1] as string;
31        i += 1;
32      } else {
33        options[key] = true;
34      }
35    } else if (arg.startsWith("-")) {
36      const short = arg.slice(1);
37      if (short === "u") {
38        if (rest[i + 1] && !rest[i + 1]!.startsWith("-")) {
39          options["uri"] = rest[i + 1] as string;
40          i += 1;
41        }
42        // else: -u with no value — uri stays unset, command handler validates
43      } else if (short === "f") {
44        options["full"] = true;
45      } else if (short === "v") {
46        options["version"] = true;
47      } else {
48        options[short] = true;
49      }
50    } else {
51      positional.push(arg);
52    }
53  }
54
55  return { command, options, positional };
56}