import type { Command } from "commander" ;
import { sanitizeForLog } from "../../terminal/ansi.js" ;
import type { NamedCommandDescriptor } from "./command-group-descriptors.js" ;
export type CommandDescriptorLike = Pick<NamedCommandDescriptor, "name" | "description" >;
const SAFE_COMMAND_NAME_PATTERN = /^[A-Za-z0-9 ][A-Za-z0-9 _-]*$/;
export type CommandDescriptorCatalog<TDescriptor extends NamedCommandDescriptor> = {
descriptors: readonly TDescriptor[];
getDescriptors: () => readonly TDescriptor[];
getNames: () => string[];
getCommandsWithSubcommands: () => string[];
};
export function normalizeCommandDescriptorName(name: string): string | null {
const normalized = name.trim();
return SAFE_COMMAND_NAME_PATTERN.test(normalized) ? normalized : null ;
}
export function assertSafeCommandDescriptorName(name: string): string {
const normalized = normalizeCommandDescriptorName(name);
if (!normalized) {
throw new Error(`Invalid CLI command name: ${JSON.stringify(name.trim())}`);
}
return normalized;
}
export function sanitizeCommandDescriptorDescription(description: string): string {
return sanitizeForLog(description).trim();
}
export function getCommandDescriptorNames(descriptors: readonly CommandDescriptorLike[]): string[] {
return descriptors.map((descriptor) => descriptor.name);
}
export function getCommandsWithSubcommands(
descriptors: readonly NamedCommandDescriptor[],
): string[] {
return descriptors
.filter((descriptor) => descriptor.hasSubcommands)
.map((descriptor) => descriptor.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;
}
export function defineCommandDescriptorCatalog<TDescriptor extends NamedCommandDescriptor>(
descriptors: readonly TDescriptor[],
): CommandDescriptorCatalog<TDescriptor> {
return {
descriptors,
getDescriptors: () => descriptors,
getNames: () => getCommandDescriptorNames(descriptors),
getCommandsWithSubcommands: () => getCommandsWithSubcommands(descriptors),
};
}
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).description(sanitizeCommandDescriptorDescription(descriptor.description));
existingCommands.add(name);
}
return existingCommands;
}
Messung V0.5 in Prozent C=96 H=96 G=95
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland