fix(cli): sanitize plugin command descriptors

This commit is contained in:
Gustavo Madeira Santana
2026-04-24 22:22:57 -04:00
parent 4da25d0125
commit 282c32db7c
7 changed files with 140 additions and 13 deletions

View File

@@ -1,8 +1,11 @@
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[];
@@ -10,6 +13,23 @@ export type CommandDescriptorCatalog<TDescriptor extends NamedCommandDescriptor>
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);
}
@@ -56,11 +76,12 @@ export function addCommandDescriptorsToProgram(
existingCommands: Set<string> = new Set(),
): Set<string> {
for (const descriptor of descriptors) {
if (existingCommands.has(descriptor.name)) {
const name = assertSafeCommandDescriptorName(descriptor.name);
if (existingCommands.has(name)) {
continue;
}
program.command(descriptor.name).description(descriptor.description);
existingCommands.add(descriptor.name);
program.command(name).description(sanitizeCommandDescriptorDescription(descriptor.description));
existingCommands.add(name);
}
return existingCommands;
}