All files parse.ts

100% Statements 40/40
100% Branches 29/29
100% Functions 2/2
100% Lines 40/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134                                                                                16x 16x         2x                   2x           2x                                           39x 39x 39x     47x   47x 47x 47x   47x 8x 2x   6x 6x 6x 6x   39x 21x 21x 3x   18x 18x 18x 18x   18x 13x 13x 1x   12x 12x 5x   7x 7x 7x 7x     5x   23x    
/**
 * Legacy flag parser for `bin/ethercalc` (ยง13 Q6).
 *
 * The old CLI used `optimist` to accept `--key`, `--cors`, `--port`, etc.
 * We preserve the flag surface verbatim so existing self-host users can
 * swap binaries without changing their systemd/init scripts. This module
 * is pure โ€” it consumes an argv array and produces a `ParsedFlags` result
 * or throws `CliError` with a message suitable for stderr. No process
 * interaction; the orchestrator in `index.ts` owns I/O and exit codes.
 */
 
/**
 * Shape of the parsed command line after legacy flag normalization.
 *
 * Every field is optional. When a flag is absent, the downstream mapper
 * (`map.ts`) either supplies a wrangler default or leaves the env var
 * unset so the Worker can fall back to its own default. Boolean flags
 * (`--cors`, `--help`) are present-or-absent; string flags carry their
 * value directly.
 */
export interface ParsedFlags {
  key?: string;
  cors?: boolean;
  port?: number;
  host?: string;
  expire?: number;
  basepath?: string;
  keyfile?: string;
  certfile?: string;
  persistTo?: string;
  help?: boolean;
}
 
/**
 * Raised when the argv contains a syntactic error (unknown flag, missing
 * value, non-numeric value where numeric required). Callers should print
 * `err.message` to stderr and exit non-zero.
 */
export class CliError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'CliError';
  }
}
 
/** Flags that take a value. Map entry โ†’ the ParsedFlags key it sets. */
const STRING_FLAGS: Record<string, keyof ParsedFlags> = {
  '--key': 'key',
  '--host': 'host',
  '--basepath': 'basepath',
  '--keyfile': 'keyfile',
  '--certfile': 'certfile',
  '--persist-to': 'persistTo',
};
 
/** Flags that take a numeric value. */
const NUMBER_FLAGS: Record<string, keyof ParsedFlags> = {
  '--port': 'port',
  '--expire': 'expire',
};
 
/** Boolean/presence flags. */
const BOOL_FLAGS: Record<string, keyof ParsedFlags> = {
  '--cors': 'cors',
  '--help': 'help',
  '-h': 'help',
};
 
/**
 * Parse argv (minus `node` + script name) into a `ParsedFlags`.
 *
 * Accepted forms, matching legacy optimist behavior:
 *   `--port 8080`
 *   `--port=8080`
 *   `--cors`          (boolean flags stand alone)
 *   `-h`              (only supported short flag โ€” matches old --help shim)
 *
 * Raises `CliError` on:
 *   - unknown flag (`--foo`)
 *   - string flag with no following value (`--port` at end of argv)
 *   - numeric flag with non-numeric value (`--port abc`)
 *   - positional arguments (we don't accept any)
 */
export function parseFlags(argv: readonly string[]): ParsedFlags {
  const out: ParsedFlags = {};
  let i = 0;
  while (i < argv.length) {
    // Loop condition keeps `raw` defined; the `as string` assertion is safe
    // and preferable to a dead defensive branch that would never be covered.
    const raw = argv[i] as string;
    // `--foo=bar` splits into two halves; `--foo bar` uses the next token.
    const eqIdx = raw.indexOf('=');
    const flag = eqIdx >= 0 ? raw.slice(0, eqIdx) : raw;
    const inlineValue = eqIdx >= 0 ? raw.slice(eqIdx + 1) : undefined;
 
    if (Object.prototype.hasOwnProperty.call(BOOL_FLAGS, flag)) {
      if (inlineValue !== undefined) {
        throw new CliError(`${flag} does not take a value`);
      }
      const key = BOOL_FLAGS[flag] as keyof ParsedFlags;
      (out as Record<string, unknown>)[key] = true;
      i += 1;
      continue;
    }
    if (Object.prototype.hasOwnProperty.call(STRING_FLAGS, flag)) {
      const value = inlineValue !== undefined ? inlineValue : argv[i + 1];
      if (value === undefined) {
        throw new CliError(`${flag} requires a value`);
      }
      const key = STRING_FLAGS[flag] as keyof ParsedFlags;
      (out as Record<string, unknown>)[key] = value;
      i += inlineValue !== undefined ? 1 : 2;
      continue;
    }
    if (Object.prototype.hasOwnProperty.call(NUMBER_FLAGS, flag)) {
      const value = inlineValue !== undefined ? inlineValue : argv[i + 1];
      if (value === undefined) {
        throw new CliError(`${flag} requires a value`);
      }
      const n = Number(value);
      if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) {
        throw new CliError(`${flag} expects a non-negative integer, got "${value}"`);
      }
      const key = NUMBER_FLAGS[flag] as keyof ParsedFlags;
      (out as Record<string, unknown>)[key] = n;
      i += inlineValue !== undefined ? 1 : 2;
      continue;
    }
    // Unknown flag OR positional argument. We don't accept either.
    throw new CliError(`Unknown flag: ${flag}`);
  }
  return out;
}