476 lines
14 KiB
JavaScript
476 lines
14 KiB
JavaScript
'use strict';
|
|
|
|
const consola = require('consola');
|
|
const utils = require('consola/utils');
|
|
|
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
|
|
const consola__default = /*#__PURE__*/_interopDefaultCompat(consola);
|
|
|
|
function toArray(val) {
|
|
if (Array.isArray(val)) {
|
|
return val;
|
|
}
|
|
return val === void 0 ? [] : [val];
|
|
}
|
|
function formatLineColumns(lines, linePrefix = "") {
|
|
const maxLengh = [];
|
|
for (const line of lines) {
|
|
for (const [i, element] of line.entries()) {
|
|
maxLengh[i] = Math.max(maxLengh[i] || 0, element.length);
|
|
}
|
|
}
|
|
return lines.map(
|
|
(l) => l.map(
|
|
(c, i) => linePrefix + c[i === 0 ? "padStart" : "padEnd"](maxLengh[i])
|
|
).join(" ")
|
|
).join("\n");
|
|
}
|
|
function resolveValue(input) {
|
|
return typeof input === "function" ? input() : input;
|
|
}
|
|
class CLIError extends Error {
|
|
constructor(message, code) {
|
|
super(message);
|
|
this.code = code;
|
|
this.name = "CLIError";
|
|
}
|
|
}
|
|
|
|
const NUMBER_CHAR_RE = /\d/;
|
|
const STR_SPLITTERS = ["-", "_", "/", "."];
|
|
function isUppercase(char = "") {
|
|
if (NUMBER_CHAR_RE.test(char)) {
|
|
return void 0;
|
|
}
|
|
return char !== char.toLowerCase();
|
|
}
|
|
function splitByCase(str, separators) {
|
|
const splitters = separators ?? STR_SPLITTERS;
|
|
const parts = [];
|
|
if (!str || typeof str !== "string") {
|
|
return parts;
|
|
}
|
|
let buff = "";
|
|
let previousUpper;
|
|
let previousSplitter;
|
|
for (const char of str) {
|
|
const isSplitter = splitters.includes(char);
|
|
if (isSplitter === true) {
|
|
parts.push(buff);
|
|
buff = "";
|
|
previousUpper = void 0;
|
|
continue;
|
|
}
|
|
const isUpper = isUppercase(char);
|
|
if (previousSplitter === false) {
|
|
if (previousUpper === false && isUpper === true) {
|
|
parts.push(buff);
|
|
buff = char;
|
|
previousUpper = isUpper;
|
|
continue;
|
|
}
|
|
if (previousUpper === true && isUpper === false && buff.length > 1) {
|
|
const lastChar = buff.at(-1);
|
|
parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
|
|
buff = lastChar + char;
|
|
previousUpper = isUpper;
|
|
continue;
|
|
}
|
|
}
|
|
buff += char;
|
|
previousUpper = isUpper;
|
|
previousSplitter = isSplitter;
|
|
}
|
|
parts.push(buff);
|
|
return parts;
|
|
}
|
|
function upperFirst(str) {
|
|
return str ? str[0].toUpperCase() + str.slice(1) : "";
|
|
}
|
|
function lowerFirst(str) {
|
|
return str ? str[0].toLowerCase() + str.slice(1) : "";
|
|
}
|
|
function pascalCase(str, opts) {
|
|
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
|
|
}
|
|
function camelCase(str, opts) {
|
|
return lowerFirst(pascalCase(str || "", opts));
|
|
}
|
|
function kebabCase(str, joiner) {
|
|
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
|
|
}
|
|
|
|
function toArr(any) {
|
|
return any == void 0 ? [] : Array.isArray(any) ? any : [any];
|
|
}
|
|
function toVal(out, key, val, opts) {
|
|
let x;
|
|
const old = out[key];
|
|
const nxt = ~opts.string.indexOf(key) ? val == void 0 || val === true ? "" : String(val) : typeof val === "boolean" ? val : ~opts.boolean.indexOf(key) ? val === "false" ? false : val === "true" || (out._.push((x = +val, x * 0 === 0) ? x : val), !!val) : (x = +val, x * 0 === 0) ? x : val;
|
|
out[key] = old == void 0 ? nxt : Array.isArray(old) ? old.concat(nxt) : [old, nxt];
|
|
}
|
|
function parseRawArgs(args = [], opts = {}) {
|
|
let k;
|
|
let arr;
|
|
let arg;
|
|
let name;
|
|
let val;
|
|
const out = { _: [] };
|
|
let i = 0;
|
|
let j = 0;
|
|
let idx = 0;
|
|
const len = args.length;
|
|
const alibi = opts.alias !== void 0;
|
|
const strict = opts.unknown !== void 0;
|
|
const defaults = opts.default !== void 0;
|
|
opts.alias = opts.alias || {};
|
|
opts.string = toArr(opts.string);
|
|
opts.boolean = toArr(opts.boolean);
|
|
if (alibi) {
|
|
for (k in opts.alias) {
|
|
arr = opts.alias[k] = toArr(opts.alias[k]);
|
|
for (i = 0; i < arr.length; i++) {
|
|
(opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
for (i = opts.boolean.length; i-- > 0; ) {
|
|
arr = opts.alias[opts.boolean[i]] || [];
|
|
for (j = arr.length; j-- > 0; ) {
|
|
opts.boolean.push(arr[j]);
|
|
}
|
|
}
|
|
for (i = opts.string.length; i-- > 0; ) {
|
|
arr = opts.alias[opts.string[i]] || [];
|
|
for (j = arr.length; j-- > 0; ) {
|
|
opts.string.push(arr[j]);
|
|
}
|
|
}
|
|
if (defaults) {
|
|
for (k in opts.default) {
|
|
name = typeof opts.default[k];
|
|
arr = opts.alias[k] = opts.alias[k] || [];
|
|
if (opts[name] !== void 0) {
|
|
opts[name].push(k);
|
|
for (i = 0; i < arr.length; i++) {
|
|
opts[name].push(arr[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const keys = strict ? Object.keys(opts.alias) : [];
|
|
for (i = 0; i < len; i++) {
|
|
arg = args[i];
|
|
if (arg === "--") {
|
|
out._ = out._.concat(args.slice(++i));
|
|
break;
|
|
}
|
|
for (j = 0; j < arg.length; j++) {
|
|
if (arg.charCodeAt(j) !== 45) {
|
|
break;
|
|
}
|
|
}
|
|
if (j === 0) {
|
|
out._.push(arg);
|
|
} else if (arg.substring(j, j + 3) === "no-") {
|
|
name = arg.slice(Math.max(0, j + 3));
|
|
if (strict && !~keys.indexOf(name)) {
|
|
return opts.unknown(arg);
|
|
}
|
|
out[name] = false;
|
|
} else {
|
|
for (idx = j + 1; idx < arg.length; idx++) {
|
|
if (arg.charCodeAt(idx) === 61) {
|
|
break;
|
|
}
|
|
}
|
|
name = arg.substring(j, idx);
|
|
val = arg.slice(Math.max(0, ++idx)) || i + 1 === len || ("" + args[i + 1]).charCodeAt(0) === 45 || args[++i];
|
|
arr = j === 2 ? [name] : name;
|
|
for (idx = 0; idx < arr.length; idx++) {
|
|
name = arr[idx];
|
|
if (strict && !~keys.indexOf(name)) {
|
|
return opts.unknown("-".repeat(j) + name);
|
|
}
|
|
toVal(out, name, idx + 1 < arr.length || val, opts);
|
|
}
|
|
}
|
|
}
|
|
if (defaults) {
|
|
for (k in opts.default) {
|
|
if (out[k] === void 0) {
|
|
out[k] = opts.default[k];
|
|
}
|
|
}
|
|
}
|
|
if (alibi) {
|
|
for (k in out) {
|
|
arr = opts.alias[k] || [];
|
|
while (arr.length > 0) {
|
|
out[arr.shift()] = out[k];
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function parseArgs(rawArgs, argsDef) {
|
|
const parseOptions = {
|
|
boolean: [],
|
|
string: [],
|
|
mixed: [],
|
|
alias: {},
|
|
default: {}
|
|
};
|
|
const args = resolveArgs(argsDef);
|
|
for (const arg of args) {
|
|
if (arg.type === "positional") {
|
|
continue;
|
|
}
|
|
if (arg.type === "string") {
|
|
parseOptions.string.push(arg.name);
|
|
} else if (arg.type === "boolean") {
|
|
parseOptions.boolean.push(arg.name);
|
|
}
|
|
if (arg.default !== void 0) {
|
|
parseOptions.default[arg.name] = arg.default;
|
|
}
|
|
if (arg.alias) {
|
|
parseOptions.alias[arg.name] = arg.alias;
|
|
}
|
|
}
|
|
const parsed = parseRawArgs(rawArgs, parseOptions);
|
|
const [...positionalArguments] = parsed._;
|
|
const parsedArgsProxy = new Proxy(parsed, {
|
|
get(target, prop) {
|
|
return target[prop] ?? target[camelCase(prop)] ?? target[kebabCase(prop)];
|
|
}
|
|
});
|
|
for (const [, arg] of args.entries()) {
|
|
if (arg.type === "positional") {
|
|
const nextPositionalArgument = positionalArguments.shift();
|
|
if (nextPositionalArgument !== void 0) {
|
|
parsedArgsProxy[arg.name] = nextPositionalArgument;
|
|
} else if (arg.default === void 0 && arg.required !== false) {
|
|
throw new CLIError(
|
|
`Missing required positional argument: ${arg.name.toUpperCase()}`,
|
|
"EARG"
|
|
);
|
|
} else {
|
|
parsedArgsProxy[arg.name] = arg.default;
|
|
}
|
|
} else if (arg.required && parsedArgsProxy[arg.name] === void 0) {
|
|
throw new CLIError(`Missing required argument: --${arg.name}`, "EARG");
|
|
}
|
|
}
|
|
return parsedArgsProxy;
|
|
}
|
|
function resolveArgs(argsDef) {
|
|
const args = [];
|
|
for (const [name, argDef] of Object.entries(argsDef || {})) {
|
|
args.push({
|
|
...argDef,
|
|
name,
|
|
alias: toArray(argDef.alias)
|
|
});
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function defineCommand(def) {
|
|
return def;
|
|
}
|
|
async function runCommand(cmd, opts) {
|
|
const cmdArgs = await resolveValue(cmd.args || {});
|
|
const parsedArgs = parseArgs(opts.rawArgs, cmdArgs);
|
|
const context = {
|
|
rawArgs: opts.rawArgs,
|
|
args: parsedArgs,
|
|
data: opts.data,
|
|
cmd
|
|
};
|
|
if (typeof cmd.setup === "function") {
|
|
await cmd.setup(context);
|
|
}
|
|
let result;
|
|
try {
|
|
const subCommands = await resolveValue(cmd.subCommands);
|
|
if (subCommands && Object.keys(subCommands).length > 0) {
|
|
const subCommandArgIndex = opts.rawArgs.findIndex(
|
|
(arg) => !arg.startsWith("-")
|
|
);
|
|
const subCommandName = opts.rawArgs[subCommandArgIndex];
|
|
if (subCommandName) {
|
|
if (!subCommands[subCommandName]) {
|
|
throw new CLIError(
|
|
`Unknown command \`${subCommandName}\``,
|
|
"E_UNKNOWN_COMMAND"
|
|
);
|
|
}
|
|
const subCommand = await resolveValue(subCommands[subCommandName]);
|
|
if (subCommand) {
|
|
await runCommand(subCommand, {
|
|
rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1)
|
|
});
|
|
}
|
|
} else if (!cmd.run) {
|
|
throw new CLIError(`No command specified.`, "E_NO_COMMAND");
|
|
}
|
|
}
|
|
if (typeof cmd.run === "function") {
|
|
result = await cmd.run(context);
|
|
}
|
|
} finally {
|
|
if (typeof cmd.cleanup === "function") {
|
|
await cmd.cleanup(context);
|
|
}
|
|
}
|
|
return { result };
|
|
}
|
|
async function resolveSubCommand(cmd, rawArgs, parent) {
|
|
const subCommands = await resolveValue(cmd.subCommands);
|
|
if (subCommands && Object.keys(subCommands).length > 0) {
|
|
const subCommandArgIndex = rawArgs.findIndex((arg) => !arg.startsWith("-"));
|
|
const subCommandName = rawArgs[subCommandArgIndex];
|
|
const subCommand = await resolveValue(subCommands[subCommandName]);
|
|
if (subCommand) {
|
|
return resolveSubCommand(
|
|
subCommand,
|
|
rawArgs.slice(subCommandArgIndex + 1),
|
|
cmd
|
|
);
|
|
}
|
|
}
|
|
return [cmd, parent];
|
|
}
|
|
|
|
async function showUsage(cmd, parent) {
|
|
try {
|
|
consola__default.log(await renderUsage(cmd, parent) + "\n");
|
|
} catch (error) {
|
|
consola__default.error(error);
|
|
}
|
|
}
|
|
async function renderUsage(cmd, parent) {
|
|
const cmdMeta = await resolveValue(cmd.meta || {});
|
|
const cmdArgs = resolveArgs(await resolveValue(cmd.args || {}));
|
|
const parentMeta = await resolveValue(parent?.meta || {});
|
|
const commandName = `${parentMeta.name ? `${parentMeta.name} ` : ""}` + (cmdMeta.name || process.argv[1]);
|
|
const argLines = [];
|
|
const posLines = [];
|
|
const commandsLines = [];
|
|
const usageLine = [];
|
|
for (const arg of cmdArgs) {
|
|
if (arg.type === "positional") {
|
|
const name = arg.name.toUpperCase();
|
|
const isRequired = arg.required !== false && arg.default === void 0;
|
|
const defaultHint = arg.default ? `="${arg.default}"` : "";
|
|
posLines.push([
|
|
"`" + name + defaultHint + "`",
|
|
arg.description || "",
|
|
arg.valueHint ? `<${arg.valueHint}>` : ""
|
|
]);
|
|
usageLine.push(isRequired ? `<${name}>` : `[${name}]`);
|
|
} else {
|
|
const isRequired = arg.required === true && arg.default === void 0;
|
|
const argStr = (arg.type === "boolean" && arg.default === true ? [
|
|
...(arg.alias || []).map((a) => `--no-${a}`),
|
|
`--no-${arg.name}`
|
|
].join(", ") : [...(arg.alias || []).map((a) => `-${a}`), `--${arg.name}`].join(
|
|
", "
|
|
)) + (arg.type === "string" && (arg.valueHint || arg.default) ? `=${arg.valueHint ? `<${arg.valueHint}>` : `"${arg.default || ""}"`}` : "");
|
|
argLines.push([
|
|
"`" + argStr + (isRequired ? " (required)" : "") + "`",
|
|
arg.description || ""
|
|
]);
|
|
if (isRequired) {
|
|
usageLine.push(argStr);
|
|
}
|
|
}
|
|
}
|
|
if (cmd.subCommands) {
|
|
const commandNames = [];
|
|
const subCommands = await resolveValue(cmd.subCommands);
|
|
for (const [name, sub] of Object.entries(subCommands)) {
|
|
const subCmd = await resolveValue(sub);
|
|
const meta = await resolveValue(subCmd?.meta);
|
|
commandsLines.push([`\`${name}\``, meta?.description || ""]);
|
|
commandNames.push(name);
|
|
}
|
|
usageLine.push(commandNames.join("|"));
|
|
}
|
|
const usageLines = [];
|
|
const version = cmdMeta.version || parentMeta.version;
|
|
usageLines.push(
|
|
utils.colors.gray(
|
|
`${cmdMeta.description} (${commandName + (version ? ` v${version}` : "")})`
|
|
),
|
|
""
|
|
);
|
|
const hasOptions = argLines.length > 0 || posLines.length > 0;
|
|
usageLines.push(
|
|
`${utils.colors.underline(utils.colors.bold("USAGE"))} \`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}\``,
|
|
""
|
|
);
|
|
if (posLines.length > 0) {
|
|
usageLines.push(utils.colors.underline(utils.colors.bold("ARGUMENTS")), "");
|
|
usageLines.push(formatLineColumns(posLines, " "));
|
|
usageLines.push("");
|
|
}
|
|
if (argLines.length > 0) {
|
|
usageLines.push(utils.colors.underline(utils.colors.bold("OPTIONS")), "");
|
|
usageLines.push(formatLineColumns(argLines, " "));
|
|
usageLines.push("");
|
|
}
|
|
if (commandsLines.length > 0) {
|
|
usageLines.push(utils.colors.underline(utils.colors.bold("COMMANDS")), "");
|
|
usageLines.push(formatLineColumns(commandsLines, " "));
|
|
usageLines.push(
|
|
"",
|
|
`Use \`${commandName} <command> --help\` for more information about a command.`
|
|
);
|
|
}
|
|
return usageLines.filter((l) => typeof l === "string").join("\n");
|
|
}
|
|
|
|
async function runMain(cmd, opts = {}) {
|
|
const rawArgs = opts.rawArgs || process.argv.slice(2);
|
|
const showUsage$1 = opts.showUsage || showUsage;
|
|
try {
|
|
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
|
|
process.exit(0);
|
|
} else if (rawArgs.length === 1 && rawArgs[0] === "--version") {
|
|
const meta = typeof cmd.meta === "function" ? await cmd.meta() : await cmd.meta;
|
|
if (!meta?.version) {
|
|
throw new CLIError("No version specified", "E_NO_VERSION");
|
|
}
|
|
consola__default.log(meta.version);
|
|
} else {
|
|
await runCommand(cmd, { rawArgs });
|
|
}
|
|
} catch (error) {
|
|
const isCLIError = error instanceof CLIError;
|
|
if (!isCLIError) {
|
|
consola__default.error(error, "\n");
|
|
}
|
|
if (isCLIError) {
|
|
await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
|
|
}
|
|
consola__default.error(error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
function createMain(cmd) {
|
|
return (opts = {}) => runMain(cmd, opts);
|
|
}
|
|
|
|
exports.createMain = createMain;
|
|
exports.defineCommand = defineCommand;
|
|
exports.parseArgs = parseArgs;
|
|
exports.renderUsage = renderUsage;
|
|
exports.runCommand = runCommand;
|
|
exports.runMain = runMain;
|
|
exports.showUsage = showUsage;
|