src/node/cli.ts/error
export const parse = (
  argv: string[],
  opts?: {
    configFile?: string
  },
): UserProvidedArgs => {
  const error = (msg: string): Error => {
    if (opts?.configFile) {
      msg = `error reading ${opts.configFile}: ${msg}`
    }

    return new Error(msg)
  }

  const args: UserProvidedArgs = {}
  let ended = false

  for (let i = 0; i < argv.length; ++i) {
    const arg = argv[i]

    // -- signals the end of option parsing.
    if (!ended && arg === "--") {
      ended = true
      continue
    }

    // Options start with a dash and require a value if non-boolean.
    if (!ended && arg.startsWith("-")) {
      let key: keyof UserProvidedArgs | undefined
      let value: string | undefined
      if (arg.startsWith("--")) {
        const split = splitOnFirstEquals(arg.replace(/^--/, ""))
        key = split[0] as keyof UserProvidedArgs
        value = split[1]
      } else {
        const short = arg.replace(/^-/, "")
        const pair = Object.entries(options).find(([, v]) => v.short === short)
        if (pair) {
          key = pair[0] as keyof UserProvidedArgs
        }
      }

      if (!key || !options[key]) {
        throw error(`Unknown option ${arg}`)
      }

      if (key === "password" && !opts?.configFile) {
        throw new Error("--password can only be set in the config file or passed in via $PASSWORD")
      }

      if (key === "hashed-password" && !opts?.configFile) {
        throw new Error("--hashed-password can only be set in the config file or passed in via $HASHED_PASSWORD")
      }

      if (key === "github-auth" && !opts?.configFile) {
        throw new Error("--github-auth can only be set in the config file or passed in via $GITHUB_TOKEN")
      }

      const option = options[key]
      if (option.type === "boolean") {
        ;(args[key] as boolean) = true
        continue
      }

      // Might already have a value if it was the --long=value format.
      if (typeof value === "undefined") {
        // A value is only valid if it doesn't look like an option.
        value = argv[i + 1] && !argv[i + 1].startsWith("-") ? argv[++i] : undefined
      }

      if (!value && option.type === OptionalString) {
        ;(args[key] as OptionalString) = new OptionalString(value)
        continue
      } else if (!value) {
        throw error(`--${key} requires a value`)
      }

      if (option.type === OptionalString && value === "false") {
        continue
      }

      if (option.path) {
        value = path.resolve(value)
      }

      switch (option.type) {
        case "string":
          ;(args[key] as string) = value
          break
        case "string[]":
          if (!args[key]) {
            ;(args[key] as string[]) = []
          }
          ;(args[key] as string[]).push(value)
          break
        case "number":
          ;(args[key] as number) = parseInt(value, 10)
          if (isNaN(args[key] as number)) {
            throw error(`--${key} must be a number`)
          }
          break
        case OptionalString:
          ;(args[key] as OptionalString) = new OptionalString(value)
          break
        default: {
          if (!Object.values(option.type).includes(value)) {
            throw error(`--${key} valid values: [${Object.values(option.type).join(", ")}]`)
          }
          ;(args[key] as string) = value
          break
        }
      }

      continue
    }

    // Everything else goes into _.
    if (typeof args._ === "undefined") {
      args._ = []
    }

    args._.push(arg)
  }

  // If a cert was provided a key must also be provided.
  if (args.cert && args.cert.value && !args["cert-key"]) {
    throw new Error("--cert-key is missing")
  }

  logger.debug(() => [`parsed ${opts?.configFile ? "config" : "command line"}`, field("args", redactArgs(args))])

  return args
}