Files
xo-cli/src/cli/arguments.ts
2026-04-20 12:26:35 +00:00

87 lines
2.5 KiB
TypeScript

/**
* CLI Argument extraction and validation.
*
* Converts `-${key}` or `--${key}` to `key` in the args object.
*/
import { z } from "zod";
/**
* Converts the CLI args to a key-value object and return the options object along with the other arguments still in the array.\
* eg: `xo-cli mnemonic create page pencil stock planet limb cluster assault speak off joke private pioneer -v -o mnemonic.txt` will return:
* {
* args: ["mnemonic", "create", "page", "pencil", "stock", "planet", "limb", "cluster", "assault", "speak", "off", "joke", "private", "pioneer"],
* options: {
* output: "mnemonic.txt",
* verbose: "true",
* },
* }
*
* @param args - The CLI args to convert.
* @returns The key-value object.
*/
export function convertArgsToObject(args: string[]): {
args: string[];
options: Record<string, string>;
} {
// Map of single-character short flags to their canonical long names
const shortToFull: Record<string, string> = {
m: "mnemonicFile",
o: "output",
v: "verbose",
h: "help",
};
// Flags that are always boolean and never consume the next argument as a value.
// Uses the canonical (expanded) names so the check works after short-form resolution.
const booleanFlags = new Set<string>([
"verbose",
"help",
"autoInputs",
"sign",
"broadcast",
"install",
]);
const positionalArgs: string[] = [];
const optionsObject: Record<string, string> = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
// Collect non-option arguments as positional args
if (!arg || !arg.startsWith("-")) {
if (arg) positionalArgs.push(arg);
continue;
}
// Format the option key:
// - Remove the leading `-`s
// - Convert kebab-case to camelCase
// - Expand known short forms to their full names
let key = arg
.replace(/^-+/, "")
.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
key = shortToFull[key] ?? key;
// Known boolean flags never take a value
if (booleanFlags.has(key)) {
optionsObject[key] = "true";
continue;
}
const nextArg = args[i + 1];
// If there's no next arg or it starts with `-`, treat this as a boolean flag
if (!nextArg || nextArg.startsWith("-")) {
optionsObject[key] = "true";
continue;
}
// Consume the next arg as the value and skip it in the next iteration
optionsObject[key] = nextArg;
i++;
}
return { args: positionalArgs, options: optionsObject };
}