feat(engine): add lightweight runtime validation and coercion for CLI arguments

This commit is contained in:
jackwener committed 2026-03-18 15:20:55 +08:00
1 parent 12c4b8853b
commit 29ea5ce059
1 file changed
+58 -1
+58 -1
View File
@@ -164,15 +164,72 @@ function registerYamlCli(filePath: string, defaultSite: string): void {
}
}
/**
* Validates and coerces arguments based on the command's Arg definitions.
*/
function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: Record<string, any>): Record<string, any> {
const result: Record<string, any> = { ...kwargs };
for (const argDef of cmdArgs) {
const val = result[argDef.name];
// 1. Check required
if (argDef.required && (val === undefined || val === null || val === '')) {
throw new Error(`Argument "${argDef.name}" is required.\n${argDef.help ? `Hint: ${argDef.help}` : ''}`);
}
if (val !== undefined && val !== null) {
// 2. Type coercion
if (argDef.type === 'int' || argDef.type === 'number') {
const num = Number(val);
if (Number.isNaN(num)) {
throw new Error(`Argument "${argDef.name}" must be a valid number. Received: "${val}"`);
}
result[argDef.name] = num;
} else if (argDef.type === 'boolean' || argDef.type === 'bool') {
if (typeof val === 'string') {
const lower = val.toLowerCase();
if (lower === 'true' || lower === '1') result[argDef.name] = true;
else if (lower === 'false' || lower === '0') result[argDef.name] = false;
else throw new Error(`Argument "${argDef.name}" must be a boolean (true/false). Received: "${val}"`);
} else {
result[argDef.name] = Boolean(val);
}
}
// 3. Choices validation
const coercedVal = result[argDef.name];
if (argDef.choices && argDef.choices.length > 0) {
// Only stringent check for string/number types against choices array
if (!argDef.choices.map(String).includes(String(coercedVal))) {
throw new Error(`Argument "${argDef.name}" must be one of: ${argDef.choices.join(', ')}. Received: "${coercedVal}"`);
}
}
} else if (argDef.default !== undefined) {
// Set default if value is missing
result[argDef.name] = argDef.default;
}
}
return result;
}
/**
* Execute a CLI command. Handles lazy-loading of TS modules.
*/
export async function executeCommand(
cmd: CliCommand,
page: IPage | null,
kwargs: Record<string, any>,
rawKwargs: Record<string, any>,
debug: boolean = false,
): Promise<any> {
let kwargs: Record<string, any>;
try {
kwargs = coerceAndValidateArgs(cmd.args, rawKwargs);
} catch (err: any) {
// Re-throw validation errors clearly
throw new Error(`[Argument Validation Error]\n${err.message}`);
}
// Lazy-load TS module on first execution
const internal = cmd as InternalCliCommand;
if (internal._lazy && internal._modulePath) {