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

alias does not seem to work #2163

Closed
ex-nerd opened this issue Apr 9, 2022 · 18 comments
Closed

alias does not seem to work #2163

ex-nerd opened this issue Apr 9, 2022 · 18 comments
Labels

Comments

@ex-nerd
Copy link

ex-nerd commented Apr 9, 2022

Aliases appear to be completely ignored with 17.4.0. Take this test script we'll call yargs.ts:

import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { basename } from 'path';

async function main(args: string[]) {
	const parser = yargs(args)
		.scriptName(`${basename(process.argv[0])} ${basename(process.argv[1])}`)
		.usage('$0 -e <env> [prop1 prop2 ...]')
		.options({
			env: {
				alias: ['e'],
				type: 'string',
				demandOption: true,
				choices: ['dev', 'stage', 'prod'] as const,
				nargs: 1,
			},
		})
		.help('h')
		.alias('h', 'help')
		.wrap(100)
		.version(false);
	// If we actually get this far, print out some debug info.
	// But the script doesn't actually get this far before printing help and exiting.
	console.log(JSON.stringify(parser, null, 2));
	const argv = await parser.parse();
	console.log(JSON.stringify(argv, null, 2));
}

if (require.main === module) {
	main(hideBin(process.argv));
}

if I run it via ts-node test.ts -e dev, the output looks like:

ts-node test.ts -e <env> [prop1 prop2 ...]

Options:
  -e, --env                                    [string] [required] [choices: "dev", "stage", "prod"]
  -h, --help  Show help                                                                    [boolean]
      ---e
      --dev

Missing required argument: env

Not only is it reporting env as missing, but it seems to be adding ---e and --dev to the list of options in the help text.

I've tried this with node 12 (what I'm officially stuck on for a few more weeks) and node 14 (just to be sure), and with yargs 16.x and 17.x with the same results.

Running ts-node test.ts --env dev works as expected and results in:

{
  "customScriptName": true,
  "parsed": false,
  "$0": "ts-node test.ts",
  "argv": {
    "_": [],
    "env": "dev",
    "--env": "dev",
    "$0": "ts-node test.ts"
  }
}
{
  "_": [],
  "env": "dev",
  "--env": "dev",
  "$0": "ts-node test.ts"
}
@jly36963 jly36963 self-assigned this Apr 9, 2022
@jly36963
Copy link
Contributor

jly36963 commented Apr 9, 2022

I have a couple of ideas on how to fix this:

use positional arg (command)

My personal opinions on this one:

  • if it's required, it should be a required positional arg (not a flag)
  • I prefer .command() over .usage(). (I feel the command pattern is easier to understand.)
const input = 'cmd1 abc'

yargs(input)
  .command(
    'cmd1 <foo>',
    'cmd1 desc',
    yargs => yargs
      .positional('foo', { type: 'string' }),
    argv => {
      console.log({ argv })
    })
  .strict()
  .parse()

use positional arg (usage)

Note: usage gets treated as an alias for command when the third/fourth args (builder/handler) are passed
usage docs

const input = 'abc'

yargs(input)
  .usage(
    '$0 <foo>',
    'default desc',
    yargs => yargs
      .positional('foo', { type: 'string' }),
    argv => {
      console.log({ argv })
    })
  .strict()
  .parse()

usage with required flag

unit test example

const input = '-f abc'

const argv = yargs(input)
  .usage('Usage: $0 --foo THING [--bar THING2]')
  .option('foo', { type: 'string', alias: 'f', demand: true })
  .option('bar', { type: 'string', alias: 'b', demand: false })
  .strict()
  .parse()

console.log({ argv })

In the unit test, you can see that flags in the usage string are required, unless wrapped with []. (demand: true could be omitted in my example, as the usage string will make it required.)

I think your example is failing because <env> gets interpreted as a required positional argument, rather than the value used with -e

@ex-nerd
Copy link
Author

ex-nerd commented Apr 9, 2022

Is usage actually adding logic? I only added it because it seemed like the only way to get that text into the help output. If it's being interpolated, that would explain some of the problems (though I'd argue that interaction is still buggy). I tried command but couldn't figure out how to then access the command value (adding command(...) seems to make the value disappear from argv['_'] but doesn't seem to put it anywhere else accessible).

I'll have to play around with this without the usage call, and then dig deeper in the docs to see how to add that info to the help text without affecting the arg parser.

@jly36963
Copy link
Contributor

jly36963 commented Apr 9, 2022

Yeah, usage will demand required option and positional arguments

@jly36963
Copy link
Contributor

jly36963 commented Apr 9, 2022

I tried command but couldn't figure out how to then access the command value

I don't understand this line. Which value?

@jly36963
Copy link
Contributor

jly36963 commented Apr 9, 2022

For a required option using .command(), I would do something like this:

const input = 'cmd1 -f abc'

yargs(input)
  .command(
    'cmd1',
    'cmd1 desc', // description could be 'Example: cmd1 -f "Hello World"'
    yargs => yargs
      .option('foo', { type: 'string', alias: 'f', required: true }),
    argv => {
      console.log({ argv })
    })
  .strict()
  .parse()

which would yield

{
  argv: {
    _: [ 'cmd1' ],
    f: 'abc',
    foo: 'abc',
    '$0': 'example.js'
  }
}

@ex-nerd
Copy link
Author

ex-nerd commented Apr 11, 2022

I still can't get aliases to work properly without commands (or with them, since in my real-world case the options are "global" not per-command so I would expect something more like ts-node test.ts -e dev command).

If you simply comment out the usage from my original code, you can see that trying to use the -e alias doesn't work. It gets the same error if I try to use .alias('env', 'e') as well, so something is still failing to parse the alias.

I played around a bit with your last example (comment just above this), and it seems like the big difference is my input (coming directly from hideBin(process.argv)) is a list [ 'cmd1', '-f', 'bar' ] rather than the string 'cmd1 -f abc' from your example.

When passing in a list, the alias does not work. But compare to passing in [ 'cmd1', '--foo', 'bar' ] then results in success.

@jly36963
Copy link
Contributor

jly36963 commented Apr 11, 2022

Here's an example with both global and command-specific options:

yargs(process.argv.slice(2))
  .command(
    '$0',
    'default desc',
    yargs => yargs,
    argv => console.log(argv)
  )
  .command(
    'cmd1',
    'cmd1 desc',
    yargs => yargs
      // chained onto command builder
      .option('bar', { type: 'string', alias: 'b', required: true }),
    argv => console.log(argv)
    )
  // chained globally
  .option('foo', { type: 'string', alias: 'f', required: true })
  .strict()
  .parse()

the output for node example.js -f abc is

{ _: [], f: 'abc', foo: 'abc', '$0': 'example.js' }

the output for node example.js cmd1 -f abc -b 123 is

{
  _: [ 'cmd1' ],
  f: 'abc',
  foo: 'abc',
  b: '123',
  bar: '123',
  '$0': 'example.js'
}

@jly36963
Copy link
Contributor

Passing a string or an array of strings to yargs() should have similar behavior. In my most recent example, I used
process.argv.slice(2),
which evaluates to
[ 'cmd1', '-f', 'abc', '-b', '123' ]

(There was a bug a while ago where extra sets of quotes were stripped differently between the two, but that should be fixed now)

@ex-nerd
Copy link
Author

ex-nerd commented Apr 11, 2022

Passing a string or an array of strings to yargs() should have similar behavior

Like I said, it doesn't do that for me. I'm currently using node 12 (stupid enterprise dependency) but had similar behavior with 14 (where we'll be in a few weeks). Happy to help debug more and figure out what's different about my environment.

I've tried your example code as such:

import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';

yargs(hideBin(process.argv))
	.command(
		'$0',
		'default desc',
		(yargs) => yargs,
		(argv) => console.log(argv),
	)
	.command(
		'cmd1',
		'cmd1 desc',
		(yargs) =>
			yargs
				// chained onto command builder
				.option('bar', { type: 'string', alias: 'b', required: true }),
		(argv) => console.log(argv),
	)
	// chained globally
	.option('foo', { type: 'string', alias: 'f', required: true })
	.strict()
	.parse();

When I invoke with ts-node test.ts -f abc the output is:

test.ts

default desc

Commands:
  test.ts       default desc                                           [default]
  test.ts cmd1  cmd1 desc

Options:
      --help     Show help                                             [boolean]
      --version  Show version number                                   [boolean]
  -f, --foo                                                  [string] [required]
      ---f
      --abc

Missing required argument: foo

The same happens if I compile with tsc and then run node test.js -f abc

Command-based required options also fail (e.g. via node test.js cmd1 -f abc -b 123):

test.js cmd1

cmd1 desc

Options:
      --123
      --help     Show help                                             [boolean]
      --version  Show version number                                   [boolean]
  -f, --foo                                                  [string] [required]
  -b, --bar                                                  [string] [required]
      --cmd1
      ---f
      --abc
      ---b

Missing required arguments: foo, bar

@jly36963
Copy link
Contributor

good to know, I'll test it with Node 12

@jly36963
Copy link
Contributor

jly36963 commented Apr 11, 2022

I could be wrong, but I think ts-node doesn't forward args, you might have to do something like this

(Update: I re-read your comment and saw that you compiled with tsc and saw the same error)

@jly36963
Copy link
Contributor

jly36963 commented Apr 11, 2022

Can you test the following example without ts? I'm wondering if there is something in the compilation process that's messing things up.

const input = process.argv.slice(2)

console.log({ input, version: process.version })

yargs(input)
  .command(
    '$0',
    'default desc',
    yargs => yargs,
    argv => console.log(argv)
  )
  .command(
    'cmd1',
    'cmd1 desc',
    yargs => yargs
      .option('bar', { type: 'string', alias: 'b', required: true }),
    argv => console.log(argv)
    )
  .option('foo', { type: 'string', alias: 'f', required: true })
  .strict()
  .parse()

When I run node example.js -f abc, I get

{ input: [ '-f', 'abc' ], version: 'v12.22.12' }
{ _: [], f: 'abc', foo: 'abc', '$0': 'example.js' }

@ex-nerd
Copy link
Author

ex-nerd commented Apr 11, 2022

node test.js -f abc

{ input: [ '-f', 'abc' ], version: 'v14.19.1' }
test.js

default desc

Commands:
  test.js       default desc                                           [default]
  test.js cmd1  cmd1 desc

Options:
      --help     Show help                                             [boolean]
      --version  Show version number                                   [boolean]
  -f, --foo                                                  [string] [required]
      ---f
      --abc

Missing required argument: foo

Similar results with node 12, too. This is yargs 17.4.1

@jly36963
Copy link
Contributor

jly36963 commented Apr 12, 2022

I have an example up on codesandbox. Can you verify that it works?

I have no idea what could be causing this issue

Screen Shot 2022-04-11 at 6 26 30 PM

@ex-nerd
Copy link
Author

ex-nerd commented Apr 12, 2022

I wonder if this is an issue with typescript. I've been putting your examples into test.ts and then compiling them. I can't just run the straight js because it doesn't build, e.g.:

(node:78603) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/ex-nerd/test.js:1
import yargs from "yargs";
^^^^^^

SyntaxError: Cannot use import statement outside a module

I tested that outside of any sort of directory structure to make sure my existing package.json wasn't affecting things.

@jly36963
Copy link
Contributor

jly36963 commented Apr 12, 2022

If you're using a version of Node that supports esm, you can add "type": "module" to your package.json. (Node uses common js / require syntax by default). You're getting the error because common js modules don't support the import syntax

If you can't modify your existing project like that, I suggest initializing a new project to test the code, or using the common js syntax (require).

My suspicion is that the typescript compilation process is messing something up

@bcoe bcoe added the question label Apr 20, 2022
@jly36963 jly36963 removed their assignment Jan 29, 2023
@shadowspawn
Copy link
Member

shadowspawn commented Feb 24, 2023

jly36963 was right with suspicion that TypeScript compilation is the problem.

I recently discovered that the command-line flags appearing in the help is a big red flag that the TypeScript import is not working as intended. The short version is turn on esModuleInterop. For more detail of current research, see: #2010 (comment)

@shadowspawn
Copy link
Member

An answer was provided, and no further activity in a month. Closing this as resolved.

Feel free to open a new issue if it comes up again, with new information and renewed interest.

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

4 participants