From b47a96700a67d8cc647a35a6ce0de1e4d01fd025 Mon Sep 17 00:00:00 2001 From: peterroe Date: Thu, 12 Oct 2023 17:14:29 +0800 Subject: [PATCH 1/5] feat: support `validate` in args definition --- src/args.ts | 12 ++++++++++++ src/command.ts | 7 +++++-- src/types.ts | 9 +++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/args.ts b/src/args.ts index 83235ae..2624ac1 100644 --- a/src/args.ts +++ b/src/args.ts @@ -75,3 +75,15 @@ 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(typeof word === 'string') { + return `Argument validation failed: ${name}` + (word ? ` - ${word}` : '') + } + } + } +} \ No newline at end of file diff --git a/src/command.ts b/src/command.ts index 77df00d..f384961 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, @@ -58,7 +58,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") { await cmd.run(context); diff --git a/src/types.ts b/src/types.ts index cd71977..261f66c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,6 +9,7 @@ export type _ArgDef = { alias?: string | string[]; default?: VT; required?: boolean; + validate?: (value: ParsedArgType) => boolean | string }; export type BooleanArgDef = _ArgDef<"boolean", boolean>; @@ -36,6 +37,14 @@ export type ParsedArgs = { _: string[] } & Record< > & Record; +export type ParsedArgType = T extends "positional" + ? string + : T extends "string" + ? string + : T extends "boolean" + ? boolean + : (string | boolean) + // ----- Command ----- // Command: Shared From 07672a30f7e6151a08993ae701612e67cd0908ca Mon Sep 17 00:00:00 2001 From: peterroe Date: Thu, 12 Oct 2023 17:17:42 +0800 Subject: [PATCH 2/5] chore: add playground example --- playground/commands/deploy.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/playground/commands/deploy.ts b/playground/commands/deploy.ts index fbbe45b..1823615 100644 --- a/playground/commands/deploy.ts +++ b/playground/commands/deploy.ts @@ -52,6 +52,14 @@ export default defineCommand({ description: "Path to the build output directory", default: ".output", }, + 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"); From 416b45ef97cec30acaed3d49cdfb697cbbb06c17 Mon Sep 17 00:00:00 2001 From: peterroe Date: Thu, 12 Oct 2023 17:18:44 +0800 Subject: [PATCH 3/5] fix: validate when `required` is `true` --- src/args.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/args.ts b/src/args.ts index 2624ac1..aebfc5a 100644 --- a/src/args.ts +++ b/src/args.ts @@ -81,7 +81,7 @@ export function resolveArgsValidate(parsedArgs: ParsedArgs, argsDef: ArgsDef): s const value = parsedArgs[name] as never if(argDef.validate) { const word = argDef.validate(value) || '' - if(typeof word === 'string') { + if(argDef.required && typeof word === 'string') { return `Argument validation failed: ${name}` + (word ? ` - ${word}` : '') } } From 30bc994cfe86e435b45ff02676660484efc7f491 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 13 Oct 2023 05:18:45 +0000 Subject: [PATCH 4/5] chore: apply automated lint fixes --- src/args.ts | 19 ++++++++++++------- src/command.ts | 4 ++-- src/types.ts | 10 +++++----- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/args.ts b/src/args.ts index aebfc5a..bd83956 100644 --- a/src/args.ts +++ b/src/args.ts @@ -76,14 +76,19 @@ export function resolveArgs(argsDef: ArgsDef): Arg[] { return args; } -export function resolveArgsValidate(parsedArgs: ParsedArgs, argsDef: ArgsDef): string | undefined { +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}` : '') + 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}` : "") + ); } } } -} \ No newline at end of file +} diff --git a/src/command.ts b/src/command.ts index f384961..4d4fded 100644 --- a/src/command.ts +++ b/src/command.ts @@ -58,8 +58,8 @@ export async function runCommand( throw new CLIError(`No command specified.`, "E_NO_COMMAND"); } } - const word = resolveArgsValidate(parsedArgs, cmdArgs) - if(word) { + const word = resolveArgsValidate(parsedArgs, cmdArgs); + if (word) { throw new CLIError(word, "E_VALIDATE_FAILED"); } // Handle main command diff --git a/src/types.ts b/src/types.ts index 261f66c..c70c98e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,7 +9,7 @@ export type _ArgDef = { alias?: string | string[]; default?: VT; required?: boolean; - validate?: (value: ParsedArgType) => boolean | string + validate?: (value: ParsedArgType) => boolean | string; }; export type BooleanArgDef = _ArgDef<"boolean", boolean>; @@ -40,10 +40,10 @@ export type ParsedArgs = { _: string[] } & Record< export type ParsedArgType = T extends "positional" ? string : T extends "string" - ? string - : T extends "boolean" - ? boolean - : (string | boolean) + ? string + : T extends "boolean" + ? boolean + : string | boolean; // ----- Command ----- From b7da5fdbfe313360bf78703bb187e427a5ea4bd2 Mon Sep 17 00:00:00 2001 From: lsh <59404696+peterroe@users.noreply.github.com> Date: Sun, 7 Jan 2024 15:43:51 +0800 Subject: [PATCH 5/5] feat: add `hidden` option --- playground/cli.ts | 1 + playground/commands/debug.ts | 25 +++++++++++++++++++++++++ src/types.ts | 1 + src/usage.ts | 3 +++ 4 files changed, 30 insertions(+) create mode 100644 playground/commands/debug.ts diff --git a/playground/cli.ts b/playground/cli.ts index fa406b3..159211b 100644 --- a/playground/cli.ts +++ b/playground/cli.ts @@ -15,6 +15,7 @@ const main = defineCommand({ subCommands: { build: () => import("./commands/build").then((r) => r.default), deploy: () => import("./commands/deploy").then((r) => r.default), + debug: () => import("./commands/debug").then((r) => r.default), }, }); diff --git a/playground/commands/debug.ts b/playground/commands/debug.ts new file mode 100644 index 0000000..3f90251 --- /dev/null +++ b/playground/commands/debug.ts @@ -0,0 +1,25 @@ +import consola from "consola"; +import { defineCommand } from "../../src"; + +export default defineCommand({ + meta: { + name: "debug", + description: "Debug the project", + hidden: true + }, + args: { + verbose: { + type: "boolean", + description: "Output more detailed debugging information", + }, + feature: { + type: "string", + default: "database-query", + description: "Only debug a specific function", + }, + }, + run({ args }) { + consola.log("Debug"); + consola.log("Parsed args:", args); + }, +}); diff --git a/src/types.ts b/src/types.ts index c70c98e..6a7f0d8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -53,6 +53,7 @@ export interface CommandMeta { name?: string; version?: string; description?: string; + hidden?: boolean; } // Command: Definition diff --git a/src/usage.ts b/src/usage.ts index 2fb0274..18813db 100644 --- a/src/usage.ts +++ b/src/usage.ts @@ -72,6 +72,9 @@ export async function renderUsage( for (const [name, sub] of Object.entries(subCommands)) { const subCmd = await resolveValue(sub); const meta = await resolveValue(subCmd?.meta); + if(meta?.hidden) { + continue; + } commandsLines.push([`\`${name}\``, meta?.description || ""]); commandNames.push(name); }