Effect-TS / effect

An ecosystem of tools to build robust applications in TypeScript
https://effect.website
MIT License
7.41k stars 231 forks source link

@effect/cli Options.repeated order of arguments matters #3729

Open joepjoosten opened 2 weeks ago

joepjoosten commented 2 weeks ago

What version of Effect is running?

effect 3.8.4, @effect/cli 0.45.2

What steps can reproduce the bug?

import { Console, Effect } from "effect";
import { NodeContext, NodeRuntime } from "@effect/platform-node";
import { Command, Options } from "@effect/cli";

const first = Options.text("first").pipe(Options.repeated);

const second = Options.text("second").pipe();

const third = Options.text("third").pipe();

const run = Command.make("example", { first, second, third }, (opts) =>
  Console.log(opts)
).pipe(
  Command.run({
    name: "Test",
    version: "1.0.0",
  })
);

const main = Effect.suspend(() => run(globalThis.process.argv));

main.pipe(
  Effect.provide(NodeContext.layer),
  Effect.tapErrorCause(Effect.logError),
  NodeRuntime.runMain
);

Then running it with the following options: --third 3 --second 2 --first 1 --first 4, or --first 1 --third 3 --second 2 --first 4 Wont work.

What is the expected behavior?

The order should not matter for the given options. (i know about sub commands, and there options / arguments must follow the sub command)

I expect it to work independent of the order, like non repeated options.

What do you see instead?

Error: {"_tag":"InvalidValue","error":{"_tag":"Paragraph","value":{"_tag":"Text","value":"Received unknown argument: '--first'"}}}

Additional information

No response

joepjoosten commented 6 days ago

Is it my correct interpretation that effect cli reduces the Options? So it passes the leftover arguments to every Options in the iteration?

This way there is possible misinterpretation of arguments like when the arguments are like this:

I have two Options: Options.text("t").pipe(Options.withDefault("default")) Options.boolean("b")

Should this --t --b be interpretted as:

{
   t: "--b",
   b: false
}

or as

{
   t: "default",
   b: true
}

It now depends on the order in which the Options are evaluated.

Maybe it's better to do it the other way around: parse the args from left to right, and try see which Options fit?