UNPKG

@lenne.tech/cli

Version:

lenne.Tech CLI: lt

162 lines (161 loc) 7.05 kB
"use strict"; /** * Generic `--help` / `-h` support for **every** lt command. * * Problem this solves: gluegun's built-in `.help()` only handles the top-level * `lt --help` (the command list). For a subcommand, `lt fullstack convert-mode * --help` simply *runs* the command — so a user who only wanted to read about a * command accidentally triggers it. {@link installHelpInterceptor} wraps every * loaded command so that, when help is requested, the command prints rich help * and returns **without executing**. * * Two levels of detail: * 1. Generic (always available) — usage, aliases and description from the * command metadata gluegun already has. * 2. Rich (opt-in) — a command module may `export const help: CommandHelp` * describing options, features, examples and configuration. The interceptor * loads it from `command.file` without running the command. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.installHelpInterceptor = installHelpInterceptor; exports.isHelpRequested = isHelpRequested; exports.loadCommandHelp = loadCommandHelp; exports.renderCommandHelp = renderCommandHelp; /** * Wrap every command's `run` so that `--help` / `-h` prints help and returns * without executing. Call once after `build().create()`, before `cli.run()`. * * Idempotent per command (guarded by `__helpWrapped`), so a command is never * double-wrapped if this runs more than once in the same process (e.g. tests). */ function installHelpInterceptor(commands, defaultCommand) { for (const command of commands || []) { if (typeof command.run !== 'function' || command.__helpWrapped) { continue; } // Leave the top-level/default command and gluegun's preloaded builtins // (`help`, `version` — they have `file === null`) to gluegun's own // `.help()`, so that `lt --help` keeps printing the brand banner + full // command list. Real file-backed subcommands (incl. `lt config help`) are // still wrapped. if (command === defaultCommand || !command.file || !(command.commandPath && command.commandPath.length)) { continue; } const originalRun = command.run; command.run = (toolbox) => { if ((toolbox === null || toolbox === void 0 ? void 0 : toolbox.print) && isHelpRequested(toolbox.parameters)) { renderCommandHelp(toolbox.print, command, loadCommandHelp(command.file)); return undefined; } return originalRun(toolbox); }; command.__helpWrapped = true; } } /** * True when the invocation asks for help (`--help` or `-h`) — but NOT for * `--help-json` (handled separately by `tools.helpJson`). */ function isHelpRequested(parameters) { const options = (parameters === null || parameters === void 0 ? void 0 : parameters.options) || {}; if (options['help-json'] === true || options.helpJson === true) { return false; } return options.help === true || options.h === true; } /** Best-effort load of a command's rich `help` export from its file. */ function loadCommandHelp(file) { if (!file) { return undefined; } try { const mod = require(file); const help = (mod && (mod.help || (mod.default && mod.default.help))); return help && typeof help === 'object' ? help : undefined; } catch (_a) { return undefined; } } /** * Render human-readable help for a command to `print`. Uses the rich `help` * definition when available, otherwise a useful generic fallback. Never runs * the command. */ function renderCommandHelp(print, command, help) { var _a, _b, _c; const { bold, cyan, dim, yellow } = print.colors; const usage = usagePath(command); const description = (help === null || help === void 0 ? void 0 : help.description) || command.description || '(no description)'; print.info(''); print.info(`${bold(usage)}${description}`); const aliases = aliasList(command, help); if (aliases.length) { print.info(dim(`Aliases: ${aliases.join(', ')}`)); } if ((_a = help === null || help === void 0 ? void 0 : help.features) === null || _a === void 0 ? void 0 : _a.length) { print.info(''); print.info(bold('What it does:')); for (const feature of help.features) { print.info(` • ${feature}`); } } print.info(''); print.info(bold('Usage:')); print.info(` ${usage} [options]`); if ((_b = help === null || help === void 0 ? void 0 : help.examples) === null || _b === void 0 ? void 0 : _b.length) { print.info(''); print.info(bold('Examples:')); for (const example of help.examples) { print.info(` ${cyan(example.startsWith('lt ') ? example : `lt ${example}`)}`); } } print.info(''); print.info(bold('Options:')); const options = [...((help === null || help === void 0 ? void 0 : help.options) || [])]; for (const option of options) { const meta = []; if (option.required) { meta.push('required'); } if ((_c = option.values) === null || _c === void 0 ? void 0 : _c.length) { meta.push(option.values.join('|')); } else if (option.type) { meta.push(option.type); } if (option.default !== undefined) { meta.push(`default: ${String(option.default)}`); } const metaText = meta.length ? dim(` (${meta.join(', ')})`) : ''; print.info(` ${option.flag.padEnd(28)} ${option.description}${metaText}`); } // Always-present global flags print.info(` ${'--help, -h'.padEnd(28)} Show this help and exit (does not run the command)`); print.info(` ${'--help-json'.padEnd(28)} Machine-readable help as JSON (where provided)`); if (!options.some((o) => o.flag === '--noConfirm')) { print.info(` ${'--noConfirm'.padEnd(28)} Skip confirmation prompts (where supported)`); } if (help === null || help === void 0 ? void 0 : help.configuration) { print.info(''); print.info(bold('Configuration (lt.config.json / lt.config.yaml):')); for (const line of help.configuration.split('\n')) { print.info(` ${line}`); } } if (!help) { print.info(''); print.info(dim('Tip: see docs/commands.md and docs/lt.config.md for full reference.')); } print.info(''); print.info(yellow('This is help output — the command was NOT executed.')); print.info(''); } function aliasList(command, help) { return (help === null || help === void 0 ? void 0 : help.aliases) || command.aliases || command.alias || []; } /** Resolve the user-facing invocation, e.g. `lt fullstack convert-mode`. */ function usagePath(command) { const path = command.commandPath && command.commandPath.length ? command.commandPath : [command.name || '']; return `lt ${path.filter(Boolean).join(' ')}`.trim(); }