arcanis / clipanion

Type-safe CLI library / framework with no runtime dependencies
https://mael.dev/clipanion/
1.12k stars 66 forks source link

required flags prevent subcommand `--help`; raise cryptic error #101

Closed cspotcode closed 2 years ago

cspotcode commented 3 years ago

Reproduction: https://replit.com/@AndrewBradley/clipanion-repro (clicking "run" is not wired up, but the code is in "index.ts" and the shell can be used to run node ./dist/index.js)

When a subcommand foo has a required option --bar, and I try to run mycli foo --help I get a cryptic error:

~/clipanion-repro$ node ./dist/index.js foo --help
Unknown Syntax Error: Command not found; did you mean:

$ ... foo <--bar #0>
While running foo --help
~/clipanion-repro$ 

I get the same error when I try to run mycli foo. In the former case, with --help, I want to see help output about the command. In the latter, instead of the cryptic error, it would be ideal for the error to specifically mention that --bar is missing, but not to suggest that the foo command is unrecognized.


// index.ts
#!/usr/bin/env node

import { Command, Cli, Builtins, Usage, Option } from 'clipanion';
import { isEnum } from 'typanion';

const envs = ['qa', 'stage', 'prod'] as const;
const envOption = Option.String('--bar', {
    required: true,
    description: 'bar option',
    validator: isEnum(envs)
});

async function main() {
    const cli = new Cli();
    cli.register(Builtins.VersionCommand);
    cli.register(Builtins.HelpCommand);
    cli.register(FooCommand);
    await cli.runExit(process.argv.slice(2), Cli.defaultContext);
}

class FooCommand extends Command {
    static paths = [['foo']];
    static usage: Usage = {
        description: `
            foo stuff
        `
    }

    env = envOption;

    async execute() {}
}

main();
seansfkelley commented 3 years ago

This is actually stranger yet, and seems to have something to do with the name of the flags and not merely that they are required. I've triple-checked this because it's really so strange, so I don't think I'm crazy.

import { Option, Cli, Command } from "clipanion";

async function main() {
  const cli = new Cli();

  cli.register(FailingCommand);
  cli.register(SucceedingCommand);

  try {
    const command = cli.process(process.argv.slice(2));
    await cli.runExit(command, {
      stdin: process.stdin,
      stdout: process.stdout,
      stderr: process.stderr,
    });
  } catch (error) {
    process.stdout.write(cli.error(error));
    process.exitCode = 1;
  }
}

class FailingCommand extends Command {
  static paths = [["fail"]];

  flag = Option.String("-f", {
    required: true,
  });

  async execute() {}
}

class SucceedingCommand extends Command {
  static paths = [["succeed"]];

  flag = Option.String("-c", {
    required: true,
  });

  async execute() {}
}

main();

And if I run it...

> yarn ts-node cli.ts fail -h
Unknown Syntax Error: Command not found; did you mean:

$ ... fail <-f #0>
While running fail -h

> yarn ts-node cli.ts succeed -h
$ ... succeed <-c #0>

Notice that the only difference in the commands, aside from their paths, is the specific character chosen for the flag.

If I change the flag in SucceedingCommand to something else like -d or -e, it also fails. Why would -c be significant?

cptpackrat commented 2 years ago

If I change the flag in SucceedingCommand to something else like -d or -e, it also fails. Why would -c be significant?

Might be related to this? https://github.com/arcanis/clipanion/blob/6f541472b5ab67c9306490b2c04e2d9b03ab3937/sources/advanced/HelpCommand.ts#L14-L23

I haven't really looked into how the help built-in works, but it looks like -c and -i are important to it.

magicdawn commented 2 years ago

and if you add Command.Default to static paths. then run cli -h, this prints help message instead of Unknown Syntax Error: Command not found; did you mean:

jakub-g commented 2 years ago

Note: This is now fixed via https://github.com/arcanis/clipanion/pull/128/files for --help explicit case, but when just running the command without any params and you have a required param like name = Option.String('--name', { required: true }); it still shows the cryptic error.

yarn cli foo       
Unknown Syntax Error: Command not found; did you mean:

$ yarn cli foo <--name #0>
While running foo