serialization.ts
1 /** 2 * Serialization and formatting helpers for CLI commands and args. 3 * 4 * Used by the `list` command, Commander --help, and build-manifest. 5 * Separated from registry.ts to keep the registry focused on types + registration. 6 */ 7 8 import type { Arg, CliCommand } from './registry.js'; 9 import { fullName, strategyLabel } from './registry.js'; 10 11 // ── Serialization ─────────────────────────────────────────────────────────── 12 13 export type SerializedArg = { 14 name: string; 15 type: string; 16 required: boolean; 17 valueRequired: boolean; 18 positional: boolean; 19 choices: string[]; 20 default: unknown; 21 help: string; 22 }; 23 24 /** Stable arg schema — every field is always present (no sparse objects). */ 25 export function serializeArg(a: Arg): SerializedArg { 26 return { 27 name: a.name, 28 type: a.type ?? 'string', 29 required: !!a.required, 30 valueRequired: !!a.valueRequired, 31 positional: !!a.positional, 32 choices: a.choices ?? [], 33 default: a.default ?? null, 34 help: a.help ?? '', 35 }; 36 } 37 38 /** Full command metadata for structured output (json/yaml). */ 39 export function serializeCommand(cmd: CliCommand) { 40 return { 41 command: fullName(cmd), 42 site: cmd.site, 43 name: cmd.name, 44 aliases: cmd.aliases ?? [], 45 description: cmd.description, 46 strategy: strategyLabel(cmd), 47 browser: !!cmd.browser, 48 args: cmd.args.map(serializeArg), 49 columns: cmd.columns ?? [], 50 domain: cmd.domain ?? null, 51 deprecated: cmd.deprecated ?? null, 52 replacedBy: cmd.replacedBy ?? null, 53 }; 54 } 55 56 // ── Formatting ────────────────────────────────────────────────────────────── 57 58 /** Human-readable arg summary: `<required> [optional]` style. */ 59 export function formatArgSummary(args: Arg[]): string { 60 return args 61 .map(a => { 62 if (a.positional) return a.required ? `<${a.name}>` : `[${a.name}]`; 63 return a.required ? `--${a.name}` : `[--${a.name}]`; 64 }) 65 .join(' '); 66 } 67 68 function summarizeChoices(choices: string[]): string { 69 if (choices.length <= 4) return choices.join(', '); 70 return `${choices.slice(0, 4).join(', ')}, ... (+${choices.length - 4} more)`; 71 } 72 73 /** Generate the --help appendix showing registry metadata not exposed by Commander. */ 74 export function formatRegistryHelpText(cmd: CliCommand): string { 75 const lines: string[] = []; 76 const choicesArgs = cmd.args.filter(a => a.choices?.length); 77 for (const a of choicesArgs) { 78 const prefix = a.positional ? `<${a.name}>` : `--${a.name}`; 79 const def = a.default != null ? ` (default: ${a.default})` : ''; 80 lines.push(` ${prefix}: ${summarizeChoices(a.choices!)}${def}`); 81 } 82 const meta: string[] = []; 83 meta.push(`Strategy: ${strategyLabel(cmd)}`); 84 meta.push(`Browser: ${cmd.browser ? 'yes' : 'no'}`); 85 if (cmd.domain) meta.push(`Domain: ${cmd.domain}`); 86 if (cmd.deprecated) meta.push(`Deprecated: ${typeof cmd.deprecated === 'string' ? cmd.deprecated : 'yes'}`); 87 if (cmd.replacedBy) meta.push(`Use instead: ${cmd.replacedBy}`); 88 if (cmd.aliases?.length) meta.push(`Aliases: ${cmd.aliases.join(', ')}`); 89 lines.push(meta.join(' | ')); 90 if (cmd.columns?.length) lines.push(`Output columns: ${cmd.columns.join(', ')}`); 91 return '\n' + lines.join('\n') + '\n'; 92 }