Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345// util.parseArgs: Node's lib/internal/util/parse_args algorithm (reimplemented from its// documented behavior), tokenizing argv into options/positionals per the Open Group// Utility Conventions (short options, grouped shorts, `--` as a terminator).import { inspect } from "../inspect";
type OptionType = "string" | "boolean";
export interface ParseArgsOptionConfig { type: OptionType; short?: string; multiple?: boolean; default?: string | boolean | string[] | boolean[];}
export interface ParseArgsConfig { args?: string[]; options?: Record<string, ParseArgsOptionConfig>; strict?: boolean; allowPositionals?: boolean; tokens?: boolean; allowNegative?: boolean;}
interface OptionToken { kind: "option"; name: string; rawName: string; index: number; value: string | undefined; inlineValue: boolean | undefined;}interface PositionalToken { kind: "positional"; index: number; value: string;}interface TerminatorToken { kind: "option-terminator"; index: number;}type Token = OptionToken | PositionalToken | TerminatorToken;
export interface ParsedArgs { values: Record<string, string | boolean | string[] | boolean[] | undefined>; positionals: string[]; tokens?: Token[];}
function argValue(value: unknown): string { let shown = inspect(value); if (shown.length > 128) shown = `${shown.slice(0, 128)}...`; return shown;}
function invalidArgType(name: string, expected: string, value: unknown): TypeError { const kind = name.includes(".") ? "property" : "argument"; const got = value === null || value === undefined ? ` Received ${value}` : typeof value === "function" ? ` Received function ${(value as { name?: string }).name || "anonymous"}` : typeof value === "object" ? ` Received an instance of ${(value as object).constructor?.name ?? "Object"}` : ` Received type ${typeof value} (${typeof value === "string" ? `'${value}'` : String(value)})`; return Object.assign(new TypeError(`The "${name}" ${kind} must be ${expected}.${got}`), { code: "ERR_INVALID_ARG_TYPE" });}
function invalidArgValue(name: string, value: unknown, reason: string): TypeError { return Object.assign(new TypeError(`The ${name.includes(".") ? "property" : "argument"} '${name}' ${reason}. Received ${argValue(value)}`), { code: "ERR_INVALID_ARG_VALUE", });}
function validateBoolean(value: unknown, name: string): void { if (typeof value !== "boolean") throw invalidArgType(name, "of type boolean", value);}function validateArray(value: unknown, name: string): void { if (!Array.isArray(value)) throw invalidArgType(name, "an instance of Array", value);}function validateObject(value: unknown, name: string): void { if (value === null || typeof value !== "object") throw invalidArgType(name, "of type object", value);}function validateString(value: unknown, name: string): void { if (typeof value !== "string") throw invalidArgType(name, "of type string", value);}function validateStringArray(value: unknown, name: string): void { validateArray(value, name); (value as unknown[]).forEach((v, i) => validateString(v, `${name}[${i}]`));}function validateBooleanArray(value: unknown, name: string): void { validateArray(value, name); (value as unknown[]).forEach((v, i) => validateBoolean(v, `${name}[${i}]`));}function validateUnion(value: unknown, name: string, union: readonly string[]): void { if (!union.includes(value as string)) throw invalidArgType(name, `('${union.join("|")}')`, value);}
function unknownOption(option: string, allowPositionals: boolean): TypeError { const suggest = allowPositionals ? `. To specify a positional argument starting with a '-', place it at the end of the command after '--', as in '-- ${JSON.stringify(option)}` : ""; return Object.assign(new TypeError(`Unknown option '${option}'${suggest}`), { code: "ERR_PARSE_ARGS_UNKNOWN_OPTION" });}function unexpectedPositional(value: string): TypeError { return Object.assign( new TypeError(`Unexpected argument '${value}'. This command does not take positional arguments`), { code: "ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL" }, );}function invalidOptionValue(message: string): TypeError { return Object.assign(new TypeError(message), { code: "ERR_PARSE_ARGS_INVALID_OPTION_VALUE" });}
function optionsGetOwn(options: Record<string, ParseArgsOptionConfig>, longOption: string, prop: "type" | "short" | "multiple"): unknown { return Object.hasOwn(options, longOption) ? options[longOption]![prop] : undefined;}
/** Open Group Utility Conventions: anything after the option may be its value, even if * it starts with a dash — greedy, like Node. */function isOptionValue(value: string | undefined): boolean { return value !== undefined && value !== null;}function isOptionLikeValue(value: string | undefined): boolean { return value !== undefined && value.length > 1 && value[0] === "-";}function isLoneShortOption(arg: string): boolean { return arg.length === 2 && arg[0] === "-" && arg[1] !== "-";}function isLoneLongOption(arg: string): boolean { return arg.length > 2 && arg.startsWith("--") && arg.indexOf("=", 3) === -1;}function isLongOptionAndValue(arg: string): boolean { return arg.length > 2 && arg.startsWith("--") && arg.indexOf("=", 3) !== -1;}function findLongOptionForShort(shortOption: string, options: Record<string, ParseArgsOptionConfig>): string { for (const [long, config] of Object.entries(options)) { if (config.short === shortOption) return long; } return shortOption;}function isShortOptionGroup(arg: string, options: Record<string, ParseArgsOptionConfig>): boolean { if (arg.length <= 2 || arg[0] !== "-" || arg[1] === "-") return false; const longOption = findLongOptionForShort(arg[1]!, options); return optionsGetOwn(options, longOption, "type") !== "string";}function isShortOptionAndValue(arg: string, options: Record<string, ParseArgsOptionConfig>): boolean { if (arg.length <= 2 || arg[0] !== "-" || arg[1] === "-") return false; const longOption = findLongOptionForShort(arg[1]!, options); return optionsGetOwn(options, longOption, "type") === "string";}
function argsToTokens(args: string[], options: Record<string, ParseArgsOptionConfig>): Token[] { const tokens: Token[] = []; let index = -1; let groupCount = 0; const remaining = [...args];
while (remaining.length > 0) { const arg = remaining.shift()!; const nextArg = remaining[0]; if (groupCount > 0) groupCount--; else index++;
if (arg === "--") { tokens.push({ kind: "option-terminator", index }); for (const value of remaining) tokens.push({ kind: "positional", index: ++index, value }); break; }
if (isLoneShortOption(arg)) { const shortOption = arg[1]!; const longOption = findLongOptionForShort(shortOption, options); let value: string | undefined; let inlineValue: boolean | undefined; if (optionsGetOwn(options, longOption, "type") === "string" && isOptionValue(nextArg)) { value = remaining.shift(); inlineValue = false; } tokens.push({ kind: "option", name: longOption, rawName: arg, index, value, inlineValue }); if (value != null) ++index; continue; }
if (isShortOptionGroup(arg, options)) { const expanded: string[] = []; for (let i = 1; i < arg.length; i++) { const shortOption = arg[i]!; const longOption = findLongOptionForShort(shortOption, options); if (optionsGetOwn(options, longOption, "type") !== "string" || i === arg.length - 1) { expanded.push(`-${shortOption}`); } else { expanded.push(`-${arg.slice(i)}`); break; } } remaining.unshift(...expanded); groupCount = expanded.length; continue; }
if (isShortOptionAndValue(arg, options)) { const shortOption = arg[1]!; const longOption = findLongOptionForShort(shortOption, options); tokens.push({ kind: "option", name: longOption, rawName: `-${shortOption}`, index, value: arg.slice(2), inlineValue: true }); continue; }
if (isLoneLongOption(arg)) { const longOption = arg.slice(2); let value: string | undefined; let inlineValue: boolean | undefined; if (optionsGetOwn(options, longOption, "type") === "string" && isOptionValue(nextArg)) { value = remaining.shift(); inlineValue = false; } tokens.push({ kind: "option", name: longOption, rawName: arg, index, value, inlineValue }); if (value != null) ++index; continue; }
if (isLongOptionAndValue(arg)) { const equalIndex = arg.indexOf("="); const longOption = arg.slice(2, equalIndex); tokens.push({ kind: "option", name: longOption, rawName: `--${longOption}`, index, value: arg.slice(equalIndex + 1), inlineValue: true }); continue; }
tokens.push({ kind: "positional", index, value: arg }); } return tokens;}
function checkOptionLikeValue(token: OptionToken): void { if (!token.inlineValue && isOptionLikeValue(token.value)) { const example = token.rawName.startsWith("--") ? `'${token.rawName}=-XYZ'` : `'--${token.name}=-XYZ' or '${token.rawName}-XYZ'`; throw invalidOptionValue(`Option '${token.rawName}' argument is ambiguous.\n` + `Did you forget to specify the option argument for '${token.rawName}'?\n` + `To specify an option argument starting with a dash use ${example}.`); }}
function checkOptionUsage(config: Required<Pick<ParseArgsConfig, "options" | "allowPositionals" | "allowNegative">>, token: OptionToken): void { let tokenName = token.name; if (!Object.hasOwn(config.options, tokenName)) { if (config.allowNegative && tokenName.startsWith("no-")) { tokenName = tokenName.slice(3); if (!Object.hasOwn(config.options, tokenName) || optionsGetOwn(config.options, tokenName, "type") !== "boolean") { throw unknownOption(token.rawName, config.allowPositionals); } } else { throw unknownOption(token.rawName, config.allowPositionals); } } const short = optionsGetOwn(config.options, tokenName, "short") as string | undefined; const shortAndLong = `${short ? `-${short}, ` : ""}--${tokenName}`; const type = optionsGetOwn(config.options, tokenName, "type"); if (type === "string" && typeof token.value !== "string") { throw invalidOptionValue(`Option '${shortAndLong} <value>' argument missing`); } if (type === "boolean" && token.value != null) { throw invalidOptionValue(`Option '${shortAndLong}' does not take an argument`); }}
function storeOption( token: OptionToken, options: Record<string, ParseArgsOptionConfig>, values: ParsedArgs["values"], allowNegative: boolean,): void { let longOption = token.name; if (longOption === "__proto__") return; let optionValue: string | boolean | undefined = token.value; if (allowNegative && longOption.startsWith("no-") && optionValue === undefined) { longOption = longOption.slice(3); optionValue = false; } const newValue = optionValue ?? true; if (optionsGetOwn(options, longOption, "multiple")) { const existing = values[longOption]; if (Array.isArray(existing)) (existing as unknown[]).push(newValue); else values[longOption] = [newValue] as string[] | boolean[]; } else { values[longOption] = newValue; }}
export function parseArgs(config: ParseArgsConfig | undefined, mainArgs: () => string[]): ParsedArgs { const cfg = config ?? {}; const args = cfg.args ?? mainArgs(); const strict = cfg.strict ?? true; const allowPositionals = cfg.allowPositionals ?? !strict; const returnTokens = cfg.tokens ?? false; const allowNegative = cfg.allowNegative ?? false; const options = cfg.options ?? {};
validateArray(args, "args"); validateBoolean(strict, "strict"); validateBoolean(allowPositionals, "allowPositionals"); validateBoolean(returnTokens, "tokens"); validateBoolean(allowNegative, "allowNegative"); validateObject(options, "options"); for (const [longOption, optionConfig] of Object.entries(options)) { validateObject(optionConfig, `options.${longOption}`); const optionType = optionConfig.type; validateUnion(optionType, `options.${longOption}.type`, ["string", "boolean"]); if (Object.hasOwn(optionConfig, "short")) { validateString(optionConfig.short, `options.${longOption}.short`); if (optionConfig.short!.length !== 1) throw invalidArgValue(`options.${longOption}.short`, optionConfig.short, "must be a single character"); } const multiple = optionConfig.multiple; if (Object.hasOwn(optionConfig, "multiple")) validateBoolean(multiple, `options.${longOption}.multiple`); const def = optionConfig.default; if (def !== undefined) { const validator = optionType === "string" ? (multiple ? validateStringArray : validateString) : (multiple ? validateBooleanArray : validateBoolean); validator(def, `options.${longOption}.default`); } }
const tokens = argsToTokens(args, options); const result: ParsedArgs = { values: Object.create(null), positionals: [] }; if (returnTokens) result.tokens = tokens;
for (const token of tokens) { if (token.kind === "option") { if (strict) { checkOptionUsage({ options, allowPositionals, allowNegative }, token); checkOptionLikeValue(token); } storeOption(token, options, result.values, allowNegative); } else if (token.kind === "positional") { if (!allowPositionals) throw unexpectedPositional(token.value); result.positionals.push(token.value); } }
for (const [longOption, optionConfig] of Object.entries(options)) { if (optionConfig.default !== undefined && result.values[longOption] === undefined) { result.values[longOption] = optionConfig.default; } }
return result;}