From b09cf723f3a5c8cc9c83b6742cd8f596bd8537bf Mon Sep 17 00:00:00 2001 From: kricsleo Date: Wed, 9 Apr 2025 22:25:08 +0800 Subject: [PATCH 1/6] WIP --- src/types.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/types.ts b/src/types.ts index e9892f6f..e2b025f2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ export type ArgType = | "number" | "enum" | "positional" + | "multiPositional" | undefined; // Args: Definition @@ -30,12 +31,17 @@ export type PositionalArgDef = Omit< _ArgDef<"positional", string>, "alias" | "options" >; +export type MultiPositionalArgDef = Omit< + _ArgDef<"multiPositional", string>, + "alias" | "options" +>; export type ArgDef = | BooleanArgDef | StringArgDef | NumberArgDef | PositionalArgDef + | MultiPositionalArgDef | EnumArgDef; export type ArgsDef = Record; @@ -59,6 +65,12 @@ type ParsedPositionalArg = T extends { type: "positional" } ? ResolveParsedArgType : never; +type ParsedMultiPositionalArg = T extends { + type: "multiPositional"; +} + ? ResolveParsedArgType + : never; + type ParsedStringArg = T extends { type: "string" } ? ResolveParsedArgType : never; @@ -87,6 +99,7 @@ type RawArgs = { // prettier-ignore type ParsedArg = T["type"] extends "positional" ? ParsedPositionalArg : + T["type"] extends "multiPositional" ? ParsedMultiPositionalArg : T["type"] extends "boolean" ? ParsedBooleanArg : T["type"] extends "string" ? ParsedStringArg : T["type"] extends "number" ? ParsedNumberArg : From c98a6840b13c3ebaaff21d45bbd8bb0e5db21457 Mon Sep 17 00:00:00 2001 From: kricsleo Date: Thu, 10 Apr 2025 16:11:14 +0800 Subject: [PATCH 2/6] feat: add support for `multiPositional` --- playground/hello.ts | 6 ++++++ src/args.ts | 16 ++++++++++++++-- src/types.ts | 4 ++-- src/usage.ts | 13 ++++++++++++- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/playground/hello.ts b/playground/hello.ts index f49be7fd..73c71078 100644 --- a/playground/hello.ts +++ b/playground/hello.ts @@ -29,6 +29,11 @@ const command = defineCommand({ default: "awesome", required: false, }, + likes: { + type: "multiPositional", + description: "Most liked things", + default: ["orange", "strawberry"], + } }, run({ args }) { consola.log(args); @@ -37,6 +42,7 @@ const command = defineCommand({ args.adj || "", args.name, args.age ? `You are ${args.age} years old.` : "", + args.likes?.length ? `You like ${args.likes.join(", ")}.` : "", ] .filter(Boolean) .join(" "); diff --git a/src/args.ts b/src/args.ts index ab09e84e..c528828b 100644 --- a/src/args.ts +++ b/src/args.ts @@ -14,7 +14,7 @@ export function parseArgs( enum: [] as (number | string)[], mixed: [] as string[], alias: {} as Record, - default: {} as Record, + default: {} as Record, }; const args = resolveArgs(argsDef); @@ -51,7 +51,19 @@ export function parseArgs( for (const [, arg] of args.entries()) { // eslint-disable-next-line unicorn/prefer-switch - if (arg.type === "positional") { + if (arg.type === "multiPositional") { + if(positionalArguments.length > 0) { + parsedArgsProxy[arg.name] = [...positionalArguments]; + positionalArguments.length = 0; + } else if (arg.default === undefined && arg.required !== false) { + throw new CLIError( + `Missing required multiPositional argument: ${arg.name.toUpperCase()}`, + "EARG", + ); + } else { + parsedArgsProxy[arg.name] = arg.default; + } + } else if (arg.type === "positional") { const nextPositionalArgument = positionalArguments.shift(); if (nextPositionalArgument !== undefined) { parsedArgsProxy[arg.name] = nextPositionalArgument; diff --git a/src/types.ts b/src/types.ts index e2b025f2..7cb3fce9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,7 +11,7 @@ export type ArgType = // Args: Definition -export type _ArgDef = { +export type _ArgDef = { type?: T; description?: string; valueHint?: string; @@ -32,7 +32,7 @@ export type PositionalArgDef = Omit< "alias" | "options" >; export type MultiPositionalArgDef = Omit< - _ArgDef<"multiPositional", string>, + _ArgDef<"multiPositional", string[]>, "alias" | "options" >; diff --git a/src/usage.ts b/src/usage.ts index 8500c263..a60bb594 100644 --- a/src/usage.ts +++ b/src/usage.ts @@ -36,7 +36,18 @@ export async function renderUsage( const usageLine = []; for (const arg of cmdArgs) { - if (arg.type === "positional") { + if (arg.type === "multiPositional") { + const name = arg.name.toUpperCase(); + const isRequired = arg.required !== false && arg.default === undefined; + // (isRequired ? " (required)" : " (optional)" + const defaultHint = arg.default ? `=[${arg.default}]` : ""; + posLines.push([ + "`" + name + defaultHint + "`", + arg.description || "", + arg.valueHint ? `<${arg.valueHint}>` : "", + ]); + usageLine.push(isRequired ? `<...${name}>` : `[...${name}]`); + } else if (arg.type === "positional") { const name = arg.name.toUpperCase(); const isRequired = arg.required !== false && arg.default === undefined; // (isRequired ? " (required)" : " (optional)" From 9b342a06d5428b043dbb144b26e629102a5dec66 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 08:11:43 +0000 Subject: [PATCH 3/6] chore: apply automated updates --- src/args.ts | 2 +- src/types.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/args.ts b/src/args.ts index c528828b..8f0b3971 100644 --- a/src/args.ts +++ b/src/args.ts @@ -52,7 +52,7 @@ export function parseArgs( for (const [, arg] of args.entries()) { // eslint-disable-next-line unicorn/prefer-switch if (arg.type === "multiPositional") { - if(positionalArguments.length > 0) { + if (positionalArguments.length > 0) { parsedArgsProxy[arg.name] = [...positionalArguments]; positionalArguments.length = 0; } else if (arg.default === undefined && arg.required !== false) { diff --git a/src/types.ts b/src/types.ts index 7cb3fce9..4a377ee8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,7 +11,10 @@ export type ArgType = // Args: Definition -export type _ArgDef = { +export type _ArgDef< + T extends ArgType, + VT extends boolean | number | string | string[], +> = { type?: T; description?: string; valueHint?: string; From d54bc0e479ebb6586bf5c61bab8f29e345f0a782 Mon Sep 17 00:00:00 2001 From: kricsleo Date: Thu, 10 Apr 2025 16:12:55 +0800 Subject: [PATCH 4/6] refactor: lint fix --- src/args.ts | 2 +- src/types.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/args.ts b/src/args.ts index c528828b..8f0b3971 100644 --- a/src/args.ts +++ b/src/args.ts @@ -52,7 +52,7 @@ export function parseArgs( for (const [, arg] of args.entries()) { // eslint-disable-next-line unicorn/prefer-switch if (arg.type === "multiPositional") { - if(positionalArguments.length > 0) { + if (positionalArguments.length > 0) { parsedArgsProxy[arg.name] = [...positionalArguments]; positionalArguments.length = 0; } else if (arg.default === undefined && arg.required !== false) { diff --git a/src/types.ts b/src/types.ts index 7cb3fce9..4a377ee8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,7 +11,10 @@ export type ArgType = // Args: Definition -export type _ArgDef = { +export type _ArgDef< + T extends ArgType, + VT extends boolean | number | string | string[], +> = { type?: T; description?: string; valueHint?: string; From bea2ab69fb7d1ded75241809b1f83a62bdd97ec9 Mon Sep 17 00:00:00 2001 From: kricsleo Date: Thu, 10 Apr 2025 16:17:57 +0800 Subject: [PATCH 5/6] test: add testcases for `multiPositional` --- test/args.test.ts | 29 +++++++++++++++++++++++++++++ test/usage.test.ts | 10 ++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/test/args.test.ts b/test/args.test.ts index dcb4b038..c12778b6 100644 --- a/test/args.test.ts +++ b/test/args.test.ts @@ -68,6 +68,28 @@ describe("args", () => { { _: [], command: "subCommand" }, ], [[], { command: { type: "positional", required: false } }, { _: [] }], + /** + * MultiPositional + */ + [ + ["subCommand1", "subCommand2"], + { command: { type: "multiPositional" } }, + { + _: ["subCommand1", "subCommand2"], + command: ["subCommand1", "subCommand2"], + }, + ], + [ + [], + { + command: { + type: "multiPositional", + default: ["subCommand1", "subCommand2"], + }, + }, + { _: [], command: ["subCommand1", "subCommand2"] }, + ], + [[], { command: { type: "multiPositional", required: false } }, { _: [] }], /** * Enum */ @@ -100,6 +122,13 @@ describe("args", () => { }, "Missing required positional argument: NAME", ], + [ + [], + { + name: { type: "multiPositional" }, + }, + "Missing required multiPositional argument: NAME", + ], [ ["--value", "three"], { value: { type: "enum", options: ["one", "two"] } }, diff --git a/test/usage.test.ts b/test/usage.test.ts index 89276024..d305b89e 100644 --- a/test/usage.test.ts +++ b/test/usage.test.ts @@ -39,6 +39,11 @@ describe("usage", () => { name: "pos", description: "A pos", }, + multiPositional: { + type: "multiPositional", + name: "pos", + description: "Multi positional", + }, enum: { type: "enum", name: "enum", @@ -58,11 +63,12 @@ describe("usage", () => { expect(usage).toMatchInlineSnapshot(` "A command (Commander) - USAGE \`Commander [OPTIONS] --foo \` + USAGE \`Commander [OPTIONS] --foo <...MULTIPOSITIONAL>\` ARGUMENTS - \`POS\` A pos + \`POS\` A pos + \`MULTIPOSITIONAL\` Multi positional OPTIONS From 7262ac8dd2081afbcc96440c05a77cd8db70d9a4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 21:38:40 +0000 Subject: [PATCH 6/6] chore: apply automated updates --- playground/hello.ts | 2 +- src/types.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/playground/hello.ts b/playground/hello.ts index 62d752fe..be9920e5 100644 --- a/playground/hello.ts +++ b/playground/hello.ts @@ -32,7 +32,7 @@ const command = defineCommand({ type: "multiPositional", description: "Most liked things", default: ["orange", "strawberry"], - } + }, }, run({ args }) { console.log(args); diff --git a/src/types.ts b/src/types.ts index 2a7ed4d5..8956d3fd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,10 +4,7 @@ export type ArgType = "boolean" | "string" | "enum" | "positional" | "multiPosit // Args: Definition -export type _ArgDef< - T extends ArgType, - VT extends boolean | number | string | string[], -> = { +export type _ArgDef = { type?: T; description?: string; valueHint?: string; @@ -25,7 +22,12 @@ export type EnumArgDef = _ArgDef<"enum", string>; export type PositionalArgDef = Omit<_ArgDef<"positional", string>, "alias" | "options">; export type MultiPositionalArgDef = Omit<_ArgDef<"multiPositional", string[]>, "alias" | "options">; -export type ArgDef = BooleanArgDef | StringArgDef | PositionalArgDef | MultiPositionalArgDef | EnumArgDef; +export type ArgDef = + | BooleanArgDef + | StringArgDef + | PositionalArgDef + | MultiPositionalArgDef + | EnumArgDef; export type ArgsDef = Record;