Skip to content

Non positional arguments of main command usage does not allow to use any other commands #133

@sausir1

Description

@sausir1

Describe the feature

Did not exactly knew is this a feature to implement, or a bug to fix, but marked as a feature.

Description

Seems like this part of the program could be improved - if main command have args and some sub-commands, citty fails to execute the underlying commands when any argument is passed. To better understand what I am trying to say is:

  1. I have a command that both have args that it accepts, and sub-commands that it can launch:
const subCommand = defineCommand({
  meta: {
    name: "subcommand",
    version: "0.0.1",
  },
  args: {
    world: {
      type: "string",
      required: true,
    },
  },
  run(context) {
    console.log("sub: citizen of planet %s!", context.args.world);
  },
});

const main = defineCommand({
  meta: {
    name: "play",
  },
  args: {
    name: {
      type: "string",
      required: false,
    },
  },
  setup(context) {
    console.log("main: hello %s", context.args?.name);
  },
  subCommands: {
    subCommand,
  },
});

If I want to launch the subCommand I would do:

$ ./example.js subCommand --world "earth"

this works fine, but if i would like to launch this:

$ ./example.js --name "Citty" subCommand --world "earth"

This would fail with the error message, "Unknown command Citty".

I think this can be easily fixed by changing the logic where the next command's name is being parsed in command.ts file's runCommand method.

   ...
      const subCommandArgIndex = opts.rawArgs.findIndex(
        (arg) => !arg.startsWith("-"),
      );
  ....

I came up with this solution for getting the next command's index:

const subCommandArgIndex = opts.rawArgs.findIndex((arg, index) => {
        const currentIsNotArg = !arg.startsWith("-"); // checks that the current arg is not a argument
        if (index === 0) {
          return currentIsNotArg;
        }
        const prevArg = opts.rawArgs[index - 1];
        // if current `arg` is not an argument, then we need to check if this is a value of a previous `arg` argument or not.
        return (
          currentIsNotArg && !prevArg.startsWith("-") && arg in subCommands
        );
      });

P.S. There's still a caveat left here - if positional argument's value matches the command's name, command might get called with bad parameters.

It also seems that positional argument with default value followed by a sub-command is also not working. Is this the desired behavior? If so, maybe ArgDef type should be extended, to always require positional arguments? Or it this too much of an edge case?

Additional information

  • Would you be willing to help implement this feature?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions