diff --git a/.changeset/unrecognized-arguments.md b/.changeset/unrecognized-arguments.md new file mode 100644 index 00000000000..3bd1b05e17c --- /dev/null +++ b/.changeset/unrecognized-arguments.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Added UnrecognizedArgument error to effect/cli for excess positional arguments. diff --git a/packages/effect/src/unstable/cli/CliError.ts b/packages/effect/src/unstable/cli/CliError.ts index 2ff929399d3..6dc8013b524 100644 --- a/packages/effect/src/unstable/cli/CliError.ts +++ b/packages/effect/src/unstable/cli/CliError.ts @@ -93,6 +93,7 @@ export type CliError = | UnknownSubcommand | ShowHelp | UserError + | UnrecognizedArgument /** * Error thrown when an unrecognized option is encountered. @@ -489,6 +490,34 @@ export class UserError extends Schema.TaggedErrorClass( readonly [TypeId] = TypeId } +/** + * Error thrown when an unrecognized argument is encountered. + * + * @category models + * @since 4.0.0 + */ +export class UnrecognizedArgument extends Schema.TaggedErrorClass( + `${TypeId}/UnrecognizedArgument` +)("UnrecognizedArgument", { + error: Schema.String +}) { + /** + * Marks this value as a CLI parsing error for runtime guards. + * + * @since 4.0.0 + */ + readonly [TypeId] = TypeId + + /** + * Retrieves the underlying error. + * + * @since 4.0.0 + */ + override get message() { + return this.error + } +} + /** * Schema for concrete CLI errors that can be reported together with help output. * @@ -508,6 +537,7 @@ export const NonShowHelpErrors: Schema.Union< typeof MissingArgument, typeof InvalidValue, typeof UnknownSubcommand, + typeof UnrecognizedArgument, typeof UserError ] > = Schema.Union([ @@ -517,6 +547,7 @@ export const NonShowHelpErrors: Schema.Union< MissingArgument, InvalidValue, UnknownSubcommand, + UnrecognizedArgument, UserError ]) diff --git a/packages/effect/src/unstable/cli/internal/command.ts b/packages/effect/src/unstable/cli/internal/command.ts index b35279b1c59..651722f8d87 100644 --- a/packages/effect/src/unstable/cli/internal/command.ts +++ b/packages/effect/src/unstable/cli/internal/command.ts @@ -291,6 +291,12 @@ const parseParams: (parsedArgs: Param.ParsedArgs, params: ReadonlyArray 0) { + return yield* new CliError.UnrecognizedArgument({ + error: `Excess arguments: ${currentArguments.join(", ")}` + }) + } + return results }) diff --git a/packages/effect/test/unstable/cli/Command.test.ts b/packages/effect/test/unstable/cli/Command.test.ts index 5dac07ac127..40af8d8bb1a 100644 --- a/packages/effect/test/unstable/cli/Command.test.ts +++ b/packages/effect/test/unstable/cli/Command.test.ts @@ -722,6 +722,34 @@ describe("Command", () => { const result = yield* Effect.flip(Cli.run(["test-failing", "--input", "test"])) assert.strictEqual(result, "Handler error") }).pipe(Effect.provide(TestLayer))) + it.effect("should fail with UnrecognizedArgument when excess positional arguments are provided to a command with no arguments", () => + Effect.gen(function*() { + const command = Command.make("test", {}, () => Effect.void) + const runCommand = Command.runWith(command, { version: "1.0.0" }) + const error = yield* Effect.flip(runCommand(["bogus1", "bogus2"])) + assert.strictEqual((error as any)._tag, "UnrecognizedArgument") + assert.isTrue(String((error as any).error).includes("Excess arguments: bogus1, bogus2")) + }).pipe(Effect.provide(TestLayer))) + + it.effect("should fail with UnrecognizedArgument when excess positional arguments are provided to a command with fixed arguments", () => + Effect.gen(function*() { + const command = Command.make("test", { + arg1: Argument.string("arg1") + }, () => Effect.void) + const runCommand = Command.runWith(command, { version: "1.0.0" }) + const error = yield* Effect.flip(runCommand(["valid", "bogus1", "bogus2"])) + assert.strictEqual((error as any)._tag, "UnrecognizedArgument") + assert.isTrue(String((error as any).error).includes("Excess arguments: bogus1, bogus2")) + }).pipe(Effect.provide(TestLayer))) + + it.effect("should not fail with UnrecognizedArgument when a command has variadic arguments", () => + Effect.gen(function*() { + const command = Command.make("test", { + args: Argument.string("args").pipe(Argument.repeated) + }, () => Effect.void) + const runCommand = Command.runWith(command, { version: "1.0.0" }) + yield* runCommand(["valid1", "valid2", "valid3"]) + }).pipe(Effect.provide(TestLayer))) }) describe("withSubcommands", () => {