80 lines
3.0 KiB
TypeScript
80 lines
3.0 KiB
TypeScript
import { type ParseError } from 'inline-schema';
|
|
import type { Command, CommandSchema } from './types';
|
|
|
|
function validateCommandCore(command: Command, schema: CommandSchema): string[] {
|
|
const errors: string[] = [];
|
|
|
|
if (schema.name !== '' && command.name !== schema.name) {
|
|
errors.push(`命令名称不匹配:期望 "${schema.name}",实际 "${command.name}"`);
|
|
}
|
|
|
|
const requiredParams = schema.params.filter(p => p.required);
|
|
const variadicParam = schema.params.find(p => p.variadic);
|
|
|
|
if (command.params.length < requiredParams.length) {
|
|
errors.push(`参数不足:至少需要 ${requiredParams.length} 个参数,实际 ${command.params.length} 个`);
|
|
}
|
|
|
|
if (!variadicParam && command.params.length > schema.params.length) {
|
|
errors.push(`参数过多:最多 ${schema.params.length} 个参数,实际 ${command.params.length} 个`);
|
|
}
|
|
|
|
const requiredOptions = Object.values(schema.options).filter(o => o.required);
|
|
for (const opt of requiredOptions) {
|
|
const hasOption = opt.name in command.options || (opt.short && opt.short in command.options);
|
|
if (!hasOption) {
|
|
errors.push(`缺少必需选项:--${opt.name}${opt.short ? ` 或 -${opt.short}` : ''}`);
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
export function applyCommandSchema(
|
|
command: Command,
|
|
schema: CommandSchema
|
|
): { command: Command; valid: true } | { command: Command; valid: false; errors: string[] } {
|
|
const errors = validateCommandCore(command, schema);
|
|
if (errors.length > 0) {
|
|
return { command, valid: false, errors };
|
|
}
|
|
|
|
const parseErrors: string[] = [];
|
|
|
|
const parsedParams: unknown[] = [...command.params];
|
|
for (let i = 0; i < command.params.length; i++) {
|
|
const paramValue = command.params[i];
|
|
const paramSchema = schema.params[i]?.schema;
|
|
|
|
if (paramSchema && typeof paramValue === 'string') {
|
|
try {
|
|
parsedParams[i] = paramSchema.parse(paramValue);
|
|
} catch (e) {
|
|
const err = e as ParseError;
|
|
parseErrors.push(`参数 "${schema.params[i]?.name}" 解析失败:${err.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const parsedOptions: Record<string, unknown> = { ...command.options };
|
|
for (const [key, value] of Object.entries(command.options)) {
|
|
const optSchema = schema.options[key] ?? (key.length === 1 ? Object.values(schema.options).find(o => o.short === key) : undefined);
|
|
if (optSchema?.schema && typeof value === 'string') {
|
|
try {
|
|
parsedOptions[key] = optSchema.schema.parse(value);
|
|
} catch (e) {
|
|
const err = e as ParseError;
|
|
parseErrors.push(`选项 "--${key}" 解析失败:${err.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const result = { ...command, params: parsedParams, options: parsedOptions };
|
|
|
|
if (parseErrors.length > 0) {
|
|
return { command: result, valid: false, errors: parseErrors };
|
|
}
|
|
|
|
return { command: result, valid: true };
|
|
}
|