File size: 2,465 Bytes
eb3f11e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | // Utilities for defining safe Commander placeholder descriptors.
import type { Command } from "commander";
import { sanitizeForLog } from "../../../packages/terminal-core/src/ansi.js";
import type { NamedCommandDescriptor } from "./command-group-descriptors.js";
/** Minimal descriptor shape used before a command is fully registered. */
type CommandDescriptorLike = Pick<NamedCommandDescriptor, "name" | "description" | "hidden">;
const SAFE_COMMAND_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
/** Normalize and validate a command descriptor name for safe Commander registration. */
export function normalizeCommandDescriptorName(name: string): string | null {
const normalized = name.trim();
return SAFE_COMMAND_NAME_PATTERN.test(normalized) ? normalized : null;
}
function assertSafeCommandDescriptorName(name: string): string {
const normalized = normalizeCommandDescriptorName(name);
if (!normalized) {
throw new Error(`Invalid CLI command name: ${JSON.stringify(name.trim())}`);
}
return normalized;
}
/** Strip unsafe terminal content from descriptor descriptions. */
export function sanitizeCommandDescriptorDescription(description: string): string {
return sanitizeForLog(description).trim();
}
/** Merge descriptor groups while keeping the first descriptor for each command name. */
export function collectUniqueCommandDescriptors<TDescriptor extends CommandDescriptorLike>(
descriptorGroups: readonly (readonly TDescriptor[])[],
): TDescriptor[] {
const seen = new Set<string>();
const descriptors: TDescriptor[] = [];
for (const group of descriptorGroups) {
for (const descriptor of group) {
if (seen.has(descriptor.name)) {
continue;
}
seen.add(descriptor.name);
descriptors.push(descriptor);
}
}
return descriptors;
}
/** Add safe placeholder commands to Commander without duplicating existing command names. */
export function addCommandDescriptorsToProgram(
program: Command,
descriptors: readonly CommandDescriptorLike[],
existingCommands: Set<string> = new Set(),
): Set<string> {
for (const descriptor of descriptors) {
const name = assertSafeCommandDescriptorName(descriptor.name);
if (existingCommands.has(name)) {
continue;
}
program
.command(name, { hidden: descriptor.hidden })
.description(sanitizeCommandDescriptorDescription(descriptor.description));
existingCommands.add(name);
}
return existingCommands;
}
|