diff --git a/playground/commands/deploy.ts b/playground/commands/deploy.ts index fe53689..e6277af 100644 --- a/playground/commands/deploy.ts +++ b/playground/commands/deploy.ts @@ -52,6 +52,14 @@ export default defineCommand({ description: "Deployment provider", valueHint: "foo|bar|baz|qux", }, + port: { + type: "string", + description: "Port number to listen on", + required: true, + validate(value) { + return Number(value) >= 1 && Number(value) <= 65_536 ? true : "Port number must be greater than 1 and less than 65536" + }, + } }, run({ args }) { consola.log("Build"); diff --git a/src/args.ts b/src/args.ts index ab09e84..4be5f42 100644 --- a/src/args.ts +++ b/src/args.ts @@ -116,3 +116,20 @@ export function resolveArgs(argsDef: ArgsDef): Arg[] { } return args; } + +export function resolveArgsValidate( + parsedArgs: ParsedArgs, + argsDef: ArgsDef, +): string | undefined { + for (const [name, argDef] of Object.entries(argsDef || {})) { + const value = parsedArgs[name] as never; + if (argDef.validate) { + const word = argDef.validate(value) || ""; + if (argDef.required && typeof word === "string") { + return ( + `Argument validation failed: ${name}` + (word ? ` - ${word}` : "") + ); + } + } + } +} diff --git a/src/command.ts b/src/command.ts index 4878197..df35640 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,6 +1,6 @@ import type { CommandContext, CommandDef, ArgsDef } from "./types"; import { CLIError, resolveValue } from "./_utils"; -import { parseArgs } from "./args"; +import { parseArgs, resolveArgsValidate } from "./args"; export function defineCommand( def: CommandDef, @@ -59,7 +59,10 @@ export async function runCommand( throw new CLIError(`No command specified.`, "E_NO_COMMAND"); } } - + const word = resolveArgsValidate(parsedArgs, cmdArgs); + if (word) { + throw new CLIError(word, "E_VALIDATE_FAILED"); + } // Handle main command if (typeof cmd.run === "function") { result = await cmd.run(context); diff --git a/src/types.ts b/src/types.ts index e9892f6..ae88cc6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,6 +17,7 @@ export type _ArgDef = { alias?: string | string[]; default?: VT; required?: boolean; + validate?: (value: ParsedArgType) => boolean | string; options?: (string | number)[]; }; @@ -100,6 +101,14 @@ export type ParsedArgs = RawArgs & { [K in keyof T as T[K] extends { alias: string[] } ? T[K]["alias"][number] : never]: ParsedArg } & Record; +export type ParsedArgType = T extends "positional" + ? string + : T extends "string" + ? string + : T extends "boolean" + ? boolean + : string | boolean; + // ----- Command ----- // Command: Shared