Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Typescript documentation. #1906

Closed
seivan opened this issue Apr 7, 2021 · 6 comments
Closed

Better Typescript documentation. #1906

seivan opened this issue Apr 7, 2021 · 6 comments
Labels

Comments

@seivan
Copy link
Contributor

seivan commented Apr 7, 2021

I would love to see how to keep things DRY while retaining type information for arguments and options.

Doing something like what's mentioned here: #1905
With the CommandModule<T, U> approach I have to manage both types and options manually

const sourceListCommand: Yargs.CommandModule<{ available?: boolean, outdated?: boolean }, {}> = { 
  builder: (x) => {
    return x.options("available", {
      boolean: true,
      conflicts: "outdated"
    }).options("outdated", {
      boolean: true,
      conflicts: "available"
    })
  },
  handler: (arg) => {
.    // Otherwise arg here will not have the flags. 
      console.log("SOURCE LIST COMMAND")

  },
 }

While the types are inferred from the builder using in-place adding of commands, I get all previous arguments and options.

    .command("source <command>", "Source Command", (yargs) => {
    .command("stuff <first> [second]", "source stuff ", (addYarg) => {
          return addYarg
            .positional("first", { type: "string" })
            .positional("second", { type: "string" })
        }, (args) => {
          console.log("RUNNING STUFF")

        })
        .command("list", "Source List", (listYarg) => {
          return listYarg.options("available", {
            boolean: true,
            conflicts: "outdated"
          }).options("outdated", {
            boolean: true,
            conflicts: "available"
          })

        }, (args) => {
           // Args here will autocomplete for 'first' and 'second' even though it's not part of its command. 
          console.log("RUNNING LIST")

        })

Is there any documentation or reading material to clear these things up?

@bcoe bcoe added the docs label Apr 7, 2021
@bcoe
Copy link
Member

bcoe commented Apr 7, 2021

@seivan we don't maintain the types for yargs, if you have any feature requests I suggest opening them here.

I do intend to update the docs to reflect the current behavior of @types/yargs.

@seivan
Copy link
Contributor Author

seivan commented Apr 7, 2021

@bcoe Why would you need to maintain them, isn't the project written in TS?
Regardless, it's not a question over types, but if the API has been design so it takes advantage over the type system.

Am I making a mistake here, or are you expected to work with untyped args?

@bcoe
Copy link
Member

bcoe commented Apr 7, 2021

@bcoe Why would you need to maintain them, isn't the project written in TS?

The type definitions in @types/yargs are significantly different than the types used by yargs. Yargs is written in TypeScript to improve development, allowing for refactors, etc., through type safety. It's not meant as a public interface (@types/yargs is better designed and thought out for this purpose).

Am I making a mistake here, or are you expected to work with untyped args?

I honestly haven't played much with the advanced features of @types/yargs, I don't know in what cases you receive a typed response and in what scenarios you do not.

My intention is to play with the feature more deeply, and document the current behavior. If there end up being any shortcomings or feature requests, they can be fixed on DefinitelyTyped.

@bcoe
Copy link
Member

bcoe commented Apr 7, 2021

Make sure you npm i @types/yargs, and then the functionality supported is pretty well enumerated here:

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/yargs/yargs-tests.ts

Edit: also, saw your request to contribute in the v17.0.0 issue, would greatly appreciate your help documenting the current behavior.

@seivan
Copy link
Contributor Author

seivan commented Apr 7, 2021

@bcoe
The impression I get from glancing over the tests is that type support, or more specifically using types in conjunction with the API is pretty poor as of now. Not to say there isn't any, but it requires a lot of repetition

That being said I did manage to get what I was looking for (THANKS!) by making a wrapper.
But this was not an example found in the tests and I assume could be used for documenting how to utilize Typescript the best.

export function createAdd(arg: Yargs.Argv): Yargs.Argv {
  return arg.command("add <name> [url]", "sample stuff", (addYarg) => {
    return addYarg
      .positional("name", { type: "string" }, demandOption: true)
      .positional("url", { type: "string" })
  }, (args) => {
     //args.name & args.url will be typed ✅
  })
}

While it does not work (out of the box) with CommandModule

const sourceAddCommand: Yargs.CommandModule = {
  command: ["add <name> [url]"],
  builder: (args) => {
    return args
      .positional("name", { type: "string", demandOption: true })
      .positional("url", { type: "string" })
  },

  handler: (args) => {
  //args & args.url ARE NOT typed ❌
  },
}

However there is partial support for just options and not positional.
But you can't have a builder and you lose descriptions, it literally skips over them when printing usage.

const sourceListCommand: Yargs.CommandModule<{}, {
  "available": {
    boolean: true,
    conflicts: "outdated",
   description: "Not visible ❌" 
   desc: "Not visible ❌",
  }, "outdated": {
    boolean: true,
    conflicts: "available",
   description: "Not visible ❌" 
   desc: "Not visible ❌" 
  }  
}> = {
  describe: "List Command",

  command: "list",
  handler: (args) => {
   // args.available & args.outdated are typed ✅
  },
}

To get typed arguments and avoid polluting the argument interface from other sub-commands in the same parent you need to wrap the entire sub-command in a function.
To rephrase: sub-command siblings will get access to each other arguments and positional if they are not wrapped in a function.

   Yargs
    .default(YargsHelper.hideBin(process.argv))
    .scriptName(ProjectPackage.name)
    .command("mother <command>", "One of the main commands", (yargs) => {

      return [yargs]
        .map(makeFirstSubCommand)
        .map(createAdd)
      [0]
})
   . command("father <command>", "Other main command with its own subs", (yargs) => {

      return [yargs]
        .map(createAnotherSubCommandUnderFather)
        .map(someMoreUnderFather)
      [0]
})
.argv

@seivan
Copy link
Contributor Author

seivan commented Apr 7, 2021

I am closing this in favour of #1899 - thanks for your time!

@seivan seivan closed this as completed Apr 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants