yargs / yargs

yargs the modern, pirate-themed successor to optimist.
https://yargs.js.org/
MIT License
11.06k stars 994 forks source link

Using camelCase (myOption) in yargs.options and ts types, while showing kebab-case (--my-option) in help and completion script #1679

Open MatthiasKunnen opened 4 years ago

MatthiasKunnen commented 4 years ago

I have not found a way to do this and would like to do the following. Given the following config, noProgress should be in the resulting argv but the CLI help and parser should use no-progress. This follows both conventions for camelCase in JS/TS and kebab-case in the CLI.

Code:

const argv = yargs
    .options({
        noProgress: {
            default: false,
            desc: 'Disable progress output, useful in terminals that do not support overwriting.',
            type: 'boolean',
        },
    })
    .parserConfiguration({
        'kebab-case-conversion': true,
    })
    .argv;

CLI help:

Options:
  --help           Show help                                           [boolean]
  --no-progress    Disable progress output, useful in terminals that do not
                   support overwriting.               [boolean] [default: false]

why
I want to use this approach instead of the camel case expansion because the camel case expansion does not give the return type I want (Using TypeScript).

const argv: {
    [x: string]: unknown;
-   'no-progress': boolean; // What I get
+   noProgress: boolean; // <- what I want
    _: string[];
    $0: string;
 }

I could maintain an interface with CLI options but that introduces the potential for the argv and interface to diverge when you forget to update either which could cause bugs.

mleguen commented 4 years ago

@MatthiasKunnen The types come from @types/yargs (a separate project) and are not a 100% match of what yargs really does. We are working at the moment to port yargs to typescript and publish native types. However, I doubt we will be able to include camel (or kebab) case expansion into the returned types.

That said, let's come back to your need. In your above example, you define the option name as noProgress, so you should have:

Isn't it what you are trying to achieve (using noProgress in your code, an having your users use no-progress in the CLI)?

MatthiasKunnen commented 4 years ago

Converting between camel and kebab case is indeed not supported by TypeScript's type system.

My problem is that given the following code:

const argv = yargs
  .options({
    noProgress: {
      default: false,
      desc: 'Disable progress output, useful in terminals that do not support overwriting.',
      type: 'boolean',
    },
  })
  .argv;

The CLI help is:

Options:
  --help        Show help                                              [boolean]
  --version     Show version number                                    [boolean]
  --noProgress  Disable progress output, useful in terminals that do not support
                overwriting.                          [boolean] [default: false]

I would like the CLI help to display --no-progress.

G-Rath commented 4 years ago

This also applies to the generated completions: they suggest --noProgress instead of --no-progress.

MatthiasKunnen commented 4 years ago

@mleguen, could you change the label from question to enhancement or feature request?

mleguen commented 4 years ago

@MatthiasKunnen, well, I am not sure yet an enhancement would be needed.

Wouldn't the following code suite your needs?

const argv = yargs
  .options({
    'no-progress': {
      default: false,
      desc: 'Disable progress output, useful in terminals that do not support overwriting.',
      type: 'boolean',
    },
  })
  .argv;

It returns both no-progress and noProgress in the parsing results, and shows --no-progress in the help message.

G-Rath commented 4 years ago

That'd likely mess with TypeScript however, as it would infer no-progress (since it can't transform the key into camelCase)

mleguen commented 4 years ago

Yes, you are right.

jasonkuhrt commented 3 years ago

TS 4.1 should be able to handle this feature now.

bcoe commented 3 years ago

@jasonkuhrt I've been nudging people towards continuing to use @types/yargs, since the delta between this project and yargs was pretty huge ... sounds like we could address this feature request by making a patch to DefinitelyTyped?

gajus commented 2 years ago

It seems this conversation died, however, it remains an outstanding problem, e.g.

const argv = yargs
  .env('CAS')
  .help()
  .options({
    'app-path': {
      demand: true,
      type: 'string',
    },
  })
  .parseSync();

export type Configuration = typeof argv;

Now, lets say that I want to mock Configuration. I cannot do just:

const configuration: Configuration = {
  appPath: '',
};

because app-path is missing.

Ideally, I would want to pick that types only include camelCase.

gajus commented 2 years ago

I tried removing all kebab-case properties, but for whatever reason that removes all properties.

const argv = yargs
  .env('CAS')
  .help()
  .options({
    'app-path': {
      demand: true,
      type: 'string',
    },
  })
  .parseSync();

type Configuration = Omit<typeof argv, `${string}-${string}`;

This causes Configuration to become:

{
  [x: string]: unknown,
  [x: number]: unknown
}
gajus commented 10 months ago

@bcoe Any update on this?

It keeps bitting us that we reference the config by both camelCase and kebab-case.

Trying to find a way to ensure that only one is surfaced.

shadowspawn commented 10 months ago

The type definitions for @types/yargs do not modify the inferred types for the parserConfiguration like strip-dashed, they always include both the plain option and the camelCase variation.

Rolling your own like attempted in https://github.com/yargs/yargs/issues/1679#issuecomment-1142386444 could perhaps do:

type OmitDashed<T> = {[K in keyof T as K extends `${infer a}-${infer b}` ? never : K]: T[K]}
type MyConfiguration = OmitDashed<typeof argv>;
shadowspawn commented 10 months ago

There is extra work required to explicitly add an option starting with no- like no-progress in the first example: https://github.com/yargs/yargs/issues/1679#issue-644148637

The default parsing behaviour will turn a command-line option --no-progress into progress: false and not store it as no-progess or noProgress.

So may need to use boolean-negation in the parserConfiguration too.

shadowspawn commented 10 months ago

The DefinitelyTyped work is a separate project so may want to open discussion issues there to discuss what is feasible to improve in the inferred types:

https://github.com/DefinitelyTyped/DefinitelyTyped/discussions?discussions_q=yargs