import { n as kebabCase, r as snakeCase, t as camelCase } from "./_chunks/libs/scule.mjs"; import { parseArgs as parseArgs$1 } from "node:util"; //#region src/_utils.ts function toArray(val) { if (Array.isArray(val)) return val; return val === void 0 ? [] : [val]; } function formatLineColumns(lines, linePrefix = "") { const maxLength = []; for (const line of lines) for (const [i, element] of line.entries()) maxLength[i] = Math.max(maxLength[i] || 0, element.length); return lines.map((l) => l.map((c, i) => linePrefix + c[i === 0 ? "padStart" : "padEnd"](maxLength[i])).join(" ")).join("\n"); } function resolveValue(input) { return typeof input === "function" ? input() : input; } var CLIError = class extends Error { code; constructor(message, code) { super(message); this.name = "CLIError"; this.code = code; } }; //#endregion //#region src/_parser.ts function parseRawArgs(args = [], opts = {}) { const booleans = new Set(opts.boolean || []); const strings = new Set(opts.string || []); const aliasMap = opts.alias || {}; const defaults = opts.default || {}; const aliasToMain = /* @__PURE__ */ new Map(); const mainToAliases = /* @__PURE__ */ new Map(); for (const [key, value] of Object.entries(aliasMap)) { const targets = value; for (const target of targets) { aliasToMain.set(key, target); if (!mainToAliases.has(target)) mainToAliases.set(target, []); mainToAliases.get(target).push(key); aliasToMain.set(target, key); if (!mainToAliases.has(key)) mainToAliases.set(key, []); mainToAliases.get(key).push(target); } } const options = {}; function getType(name) { if (booleans.has(name)) return "boolean"; const aliases = mainToAliases.get(name) || []; for (const alias of aliases) if (booleans.has(alias)) return "boolean"; return "string"; } function isStringType(name) { if (strings.has(name)) return true; const aliases = mainToAliases.get(name) || []; for (const alias of aliases) if (strings.has(alias)) return true; return false; } const allOptions = new Set([ ...booleans, ...strings, ...Object.keys(aliasMap), ...Object.values(aliasMap).flat(), ...Object.keys(defaults) ]); for (const name of allOptions) if (!options[name]) options[name] = { type: getType(name), default: defaults[name] }; for (const [alias, main] of aliasToMain.entries()) if (alias.length === 1 && options[main] && !options[main].short) options[main].short = alias; const processedArgs = []; const negatedFlags = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === "--") { processedArgs.push(...args.slice(i)); break; } if (arg.startsWith("--no-")) { const flagName = arg.slice(5); negatedFlags[flagName] = true; continue; } processedArgs.push(arg); } let parsed; try { parsed = parseArgs$1({ args: processedArgs, options: Object.keys(options).length > 0 ? options : void 0, allowPositionals: true, strict: false }); } catch { parsed = { values: {}, positionals: processedArgs }; } const out = { _: [] }; out._ = parsed.positionals; for (const [key, value] of Object.entries(parsed.values)) { let coerced = value; if (getType(key) === "boolean" && typeof value === "string") coerced = value !== "false"; else if (isStringType(key) && typeof value === "boolean") coerced = ""; out[key] = coerced; } for (const [name] of Object.entries(negatedFlags)) { out[name] = false; const mainName = aliasToMain.get(name); if (mainName) out[mainName] = false; const aliases = mainToAliases.get(name); if (aliases) for (const alias of aliases) out[alias] = false; } for (const [alias, main] of aliasToMain.entries()) { if (out[alias] !== void 0 && out[main] === void 0) out[main] = out[alias]; if (out[main] !== void 0 && out[alias] === void 0) out[alias] = out[main]; if (out[alias] !== out[main] && defaults[main] === out[main]) out[main] = out[alias]; } return out; } //#endregion //#region src/_color.ts const noColor = /* @__PURE__ */ (() => { const env = globalThis.process?.env ?? {}; return env.NO_COLOR === "1" || env.TERM === "dumb" || env.TEST || env.CI; })(); const _c = (c, r = 39) => (t) => noColor ? t : `\u001B[${c}m${t}\u001B[${r}m`; const bold = /* @__PURE__ */ _c(1, 22); const cyan = /* @__PURE__ */ _c(36); const gray = /* @__PURE__ */ _c(90); const underline = /* @__PURE__ */ _c(4, 24); //#endregion //#region src/args.ts function parseArgs(rawArgs, argsDef) { const parseOptions = { boolean: [], string: [], alias: {}, default: {} }; const args = resolveArgs(argsDef); for (const arg of args) { if (arg.type === "positional") continue; if (arg.type === "string" || arg.type === "enum") 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 camelName = camelCase(arg.name); const kebabName = kebabCase(arg.name); if (camelName !== arg.name || kebabName !== arg.name) { const existingAliases = toArray(parseOptions.alias[arg.name] || []); if (camelName !== arg.name && !existingAliases.includes(camelName)) existingAliases.push(camelName); if (kebabName !== arg.name && !existingAliases.includes(kebabName)) existingAliases.push(kebabName); if (existingAliases.length > 0) parseOptions.alias[arg.name] = existingAliases; } } 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.type === "enum") { const argument = parsedArgsProxy[arg.name]; const options = arg.options || []; if (argument !== void 0 && options.length > 0 && !options.includes(argument)) throw new CLIError(`Invalid value for argument: ${cyan(`--${arg.name}`)} (${cyan(argument)}). Expected one of: ${options.map((o) => cyan(o)).join(", ")}.`, "EARG"); } 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; } //#endregion //#region src/plugin.ts function defineCittyPlugin(plugin) { return plugin; } async function resolvePlugins(plugins) { return Promise.all(plugins.map((p) => resolveValue(p))); } //#endregion //#region src/command.ts 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 }; const plugins = await resolvePlugins(cmd.plugins ?? []); let result; let runError; try { for (const plugin of plugins) await plugin.setup?.(context); if (typeof cmd.setup === "function") await cmd.setup(context); const subCommands = await resolveValue(cmd.subCommands); if (subCommands && Object.keys(subCommands).length > 0) { const subCommandArgIndex = findSubCommandIndex(opts.rawArgs, cmdArgs); const explicitName = opts.rawArgs[subCommandArgIndex]; if (explicitName) { const subCommand = await _findSubCommand(subCommands, explicitName); if (!subCommand) throw new CLIError(`Unknown command ${cyan(explicitName)}`, "E_UNKNOWN_COMMAND"); await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) }); } else { const defaultSubCommand = await resolveValue(cmd.default); if (defaultSubCommand) { if (cmd.run) throw new CLIError(`Cannot specify both 'run' and 'default' on the same command.`, "E_DEFAULT_CONFLICT"); const subCommand = await _findSubCommand(subCommands, defaultSubCommand); if (!subCommand) throw new CLIError(`Default sub command ${cyan(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND"); await runCommand(subCommand, { rawArgs: opts.rawArgs }); } else if (!cmd.run) throw new CLIError(`No command specified.`, "E_NO_COMMAND"); } } if (typeof cmd.run === "function") result = await cmd.run(context); } catch (error) { runError = error; } const cleanupErrors = []; if (typeof cmd.cleanup === "function") try { await cmd.cleanup(context); } catch (error) { cleanupErrors.push(error); } for (const plugin of [...plugins].reverse()) try { await plugin.cleanup?.(context); } catch (error) { cleanupErrors.push(error); } if (runError) throw runError; if (cleanupErrors.length === 1) throw cleanupErrors[0]; if (cleanupErrors.length > 1) throw new Error("Multiple cleanup errors", { cause: cleanupErrors }); return { result }; } async function resolveSubCommand(cmd, rawArgs, parent) { const subCommands = await resolveValue(cmd.subCommands); if (subCommands && Object.keys(subCommands).length > 0) { const subCommandArgIndex = findSubCommandIndex(rawArgs, await resolveValue(cmd.args || {})); const subCommandName = rawArgs[subCommandArgIndex]; const subCommand = await _findSubCommand(subCommands, subCommandName); if (subCommand) return resolveSubCommand(subCommand, rawArgs.slice(subCommandArgIndex + 1), cmd); } return [cmd, parent]; } async function _findSubCommand(subCommands, name) { if (name in subCommands) return resolveValue(subCommands[name]); for (const sub of Object.values(subCommands)) { const resolved = await resolveValue(sub); const meta = await resolveValue(resolved?.meta); if (meta?.alias) { if (toArray(meta.alias).includes(name)) return resolved; } } } function findSubCommandIndex(rawArgs, argsDef) { for (let i = 0; i < rawArgs.length; i++) { const arg = rawArgs[i]; if (arg === "--") return -1; if (arg.startsWith("-")) { if (!arg.includes("=") && _isValueFlag(arg, argsDef)) i++; continue; } return i; } return -1; } function _isValueFlag(flag, argsDef) { const name = flag.replace(/^-{1,2}/, ""); const normalized = camelCase(name); for (const [key, def] of Object.entries(argsDef)) { if (def.type !== "string" && def.type !== "enum") continue; if (normalized === camelCase(key)) return true; if ((Array.isArray(def.alias) ? def.alias : def.alias ? [def.alias] : []).includes(name)) return true; } return false; } //#endregion //#region src/usage.ts async function showUsage(cmd, parent) { try { console.log(await renderUsage(cmd, parent) + "\n"); } catch (error) { console.error(error); } } const negativePrefixRe = /^no[-A-Z]/; 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; posLines.push([cyan(name + renderValueHint(arg)), renderDescription(arg, isRequired)]); usageLine.push(isRequired ? `<${name}>` : `[${name}]`); } else { const isRequired = arg.required === true && arg.default === void 0; const argStr = [...(arg.alias || []).map((a) => `-${a}`), `--${arg.name}`].join(", ") + renderValueHint(arg); argLines.push([cyan(argStr), renderDescription(arg, isRequired)]); /** * print negative boolean arg variant usage when * - enabled by default or has `negativeDescription` * - not prefixed with `no-` or `no[A-Z]` */ if (arg.type === "boolean" && (arg.default === true || arg.negativeDescription) && !negativePrefixRe.test(arg.name)) { const negativeArgStr = [...(arg.alias || []).map((a) => `--no-${a}`), `--no-${arg.name}`].join(", "); argLines.push([cyan(negativeArgStr), [arg.negativeDescription, isRequired ? gray("(Required)") : ""].filter(Boolean).join(" ")]); } if (isRequired) usageLine.push(`--${arg.name}` + renderValueHint(arg)); } if (cmd.subCommands) { const commandNames = []; const subCommands = await resolveValue(cmd.subCommands); for (const [name, sub] of Object.entries(subCommands)) { const meta = await resolveValue((await resolveValue(sub))?.meta); if (meta?.hidden) continue; const aliases = toArray(meta?.alias); const label = [name, ...aliases].join(", "); commandsLines.push([cyan(label), meta?.description || ""]); commandNames.push(name, ...aliases); } usageLine.push(commandNames.join("|")); } const usageLines = []; const version = cmdMeta.version || parentMeta.version; usageLines.push(gray(`${cmdMeta.description} (${commandName + (version ? ` v${version}` : "")})`), ""); const hasOptions = argLines.length > 0 || posLines.length > 0; usageLines.push(`${underline(bold("USAGE"))} ${cyan(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, ""); if (posLines.length > 0) { usageLines.push(underline(bold("ARGUMENTS")), ""); usageLines.push(formatLineColumns(posLines, " ")); usageLines.push(""); } if (argLines.length > 0) { usageLines.push(underline(bold("OPTIONS")), ""); usageLines.push(formatLineColumns(argLines, " ")); usageLines.push(""); } if (commandsLines.length > 0) { usageLines.push(underline(bold("COMMANDS")), ""); usageLines.push(formatLineColumns(commandsLines, " ")); usageLines.push("", `Use ${cyan(`${commandName} --help`)} for more information about a command.`); } return usageLines.filter((l) => typeof l === "string").join("\n"); } function renderValueHint(arg) { const valueHint = arg.valueHint ? `=<${arg.valueHint}>` : ""; const fallbackValueHint = valueHint || `=<${snakeCase(arg.name)}>`; if (!arg.type || arg.type === "positional" || arg.type === "boolean") return valueHint; if (arg.type === "enum" && arg.options?.length) return `=<${arg.options.join("|")}>`; return fallbackValueHint; } function renderDescription(arg, required) { const requiredHint = required ? gray("(Required)") : ""; const defaultHint = arg.default === void 0 ? "" : gray(`(Default: ${arg.default})`); return [ arg.description, requiredHint, defaultHint ].filter(Boolean).join(" "); } //#endregion //#region src/main.ts async function runMain(cmd, opts = {}) { const rawArgs = opts.rawArgs || process.argv.slice(2); const showUsage$1 = opts.showUsage || showUsage; try { const builtinFlags = await _resolveBuiltinFlags(cmd); if (builtinFlags.help.length > 0 && rawArgs.some((arg) => builtinFlags.help.includes(arg))) { await showUsage$1(...await resolveSubCommand(cmd, rawArgs)); process.exit(0); } else if (rawArgs.length === 1 && builtinFlags.version.includes(rawArgs[0])) { const meta = typeof cmd.meta === "function" ? await cmd.meta() : await cmd.meta; if (!meta?.version) throw new CLIError("No version specified", "E_NO_VERSION"); console.log(meta.version); } else await runCommand(cmd, { rawArgs }); } catch (error) { if (error instanceof CLIError) { await showUsage$1(...await resolveSubCommand(cmd, rawArgs)); console.error(error.message); } else console.error(error, "\n"); process.exit(1); } } function createMain(cmd) { return (opts = {}) => runMain(cmd, opts); } async function _resolveBuiltinFlags(cmd) { const argsDef = await resolveValue(cmd.args || {}); const userNames = /* @__PURE__ */ new Set(); const userAliases = /* @__PURE__ */ new Set(); for (const [name, def] of Object.entries(argsDef)) { userNames.add(name); for (const alias of toArray(def.alias)) userAliases.add(alias); } return { help: _getBuiltinFlags("help", "h", userNames, userAliases), version: _getBuiltinFlags("version", "v", userNames, userAliases) }; } function _getBuiltinFlags(long, short, userNames, userAliases) { if (userNames.has(long) || userAliases.has(long)) return []; if (userNames.has(short) || userAliases.has(short)) return [`--${long}`]; return [`--${long}`, `-${short}`]; } //#endregion export { createMain, defineCittyPlugin, defineCommand, parseArgs, renderUsage, runCommand, runMain, showUsage };