Giter Site home page Giter Site logo

getopts's People

Contributors

dependabot[bot] avatar jimt avatar jorgebucaran avatar jridgewell avatar ovyerus avatar toddself avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

getopts's Issues

Question: Is this package vulnerable to prototype pollution?

Popular argument parsing libraries such as minimist and yargs-parser have been hit by prototype pollution before. Is this library affected as well?

It seems that this is related to the "parse dot options as object" feature, so I suspect the answer is no. However, I'd like to ask just in case.

Also, I see multiple uses of for...in loops in the code. While it would not directly cause prototype pollution, would it be affected by objects that have inherited enumerable properties?

See also:

ESM imports TypeScript types

If I understand correctly, the TypeScript types are for the CommonJS version of the code. When using "module": "ES2022" in tsconfig.json with allowSyntheticDefaultImports: false, importing getopts fails unless the type definitions are changed from:

export = getopts

to

export default getopts

It might be possible to resolve this by providing different type definitions for CommonJS and ESM (different on only this one line) and using an extension to package.json that TypeScript 4.7 added (link) that lets TypeScript pick between type definition files based on the project's module type.

Alternatively, enabling allowSyntheticDefaultImports for the project seems to fix it, too.

Is the current API repetitive?

No intentions to release a new major soon, but I'd like to leave the question here lest I forget.

Don't you feel the current API is repetitive? We could trade less repetition for some verbosity:

getopts(process.argv.slice(2), {
  options: {
    foo: {
      alias: ["f", "F"],
      boolean: true,
      default: false
    },
    bar: {
      default: 42
    },
    baz: {
      alias: ["B", "bazmut"],
      string: true,
      default: "hello"
    }
  },
  stopEarly: true
})

For comparison, here's how you'd do it using the current API:

getopts(process.argv.slice(2), {
  alias: {
    foo: ["f", "F"],
    baz: ["B", "bazmut"]
  },
  default: {
    foo: false,
    bar: 42,
    baz: "hello"
  },
  boolean: ["foo"],
  stopEarly: true
})

Proper key for unknown arguments

Problem
After parsing arguments with getopts, there is usually some validation to perform. Validation libraries such as joi or ow can validate the entire resulting object. However, that validation gets harder since the resulting object includes all the unknown arguments.

Separating unknown arguments currently requires something like this:

Example:

// example.js
const getopts = require( 'getopts' );
const unexpected = {};
const args = getopts(
    process.argv.splice(2),
    {
        default: { good: 10 },
	unknown: k => unexpected[ k ] = 1
    }
);
// node example.js --bad 20
console.log( args ); // { _: [], good: 10, bad: 20 }
for ( const k of Object.keys( args ) ) {
	if ( unexpected[ k ] ) {
		unexpected[ k ] = args[ k ];
		delete args[ k ];
	}
}
console.log( args ); // { _: [], good: 10 }
console.log( unexpected ); // { bad: 20 }

Proposed Solution
Use a key such as __ (double underscore) for unknown arguments.

Example:

// example.js
const getopts = require( 'getopts' );
const args = getopts(
    process.argv.splice(2),
    {
        default: { good: 10 }
    }
);
// node example.js --bad 20
console.log( args ); // { _: [], __: { bad: 20 }, good: 10 }

Built-in TypeScript support and autogeneration of arguments, based on params

  1. Add TypeScript typings
  2. Autogenerate arguments typings (so-called type inference) based on the supplied params.
A similar solution with yargs

This feature should be built-in for type safety.

export const builder = {
  dynamodb: {
    type: `boolean`,
    default: false,
    describe: `Set up local dynamodb instance`,
  },
  clearPorts: {
    type: `array`,
    default: [],
    describe: `Remove processes from the specified Ports Number[]`,
  },
} as const
type builder = typeof builder
type cmds = keyof builder
type getType<key extends cmds> = builder[key]["type"] extends "string"
  ? string
  : builder[key]["type"] extends "boolean"
  ? boolean
  : builder[key]["type"] extends "number"
  ? number
  : builder[key]["type"] extends "array"
  ? any[]
  : unknown
export type yargsArguments = {
  [key in cmds]: getType<key>
}
Screen Shot 2021-06-07 at 9 13 15 AM

Option to remove aliases from the resulting object

Problem
After parsing arguments with getopts, there is usually some validation to perform. Validation libraries such as joi or ow can validate the entire resulting object. However, that validation gets harder since the resulting object includes the aliases' values.

Example:

// example.js
const getopts = require( 'getopts' );
const args = getopts(
    process.argv.splice(2),
    {
        alias: { maxRandomSize: [ "max-random-size", "s" ] }
    }
);
// node example.js -s 50
console.log( args ); // { _: [], s: 50, maxRandomSize: 50, 'max-random-size': 50 }

Proposed Solution
A boolean option such as removeAliases or hideAliases.

Example:

// example.js
const getopts = require( 'getopts' );
const args = getopts(
    process.argv.splice(2),
    {
        alias: { maxRandomSize: [ "max-random-size", "s" ] },
        removeAliases: true
    }
);
// node example.js -s 50
console.log( args ); // { _: [], maxRandomSize: 50 }

Current workaround

// example.js
const getopts = require( 'getopts' );
const aliases = {
	maxRandomSize: [ "max-random-size", "s" ],
	help: 'h'
};
const args = getopts(
    process.argv.splice(2),
    {
        alias: aliases
    }
);

for ( const k of Object.keys( aliases ) ) {
	const v = aliases[ k ];
	if ( 'string' === typeof v ) {
		delete args[ v ];
		continue;
	}	
	for ( const e of ( v || [] ) ) {
		delete args[ e ];
	}
}
// node example.js -s 50 -h
console.log( args ); // { _: [], maxRandomSize: 50, help: true }

Add options.array

It would set the property to an array regardless if one flag is passed or multiple flags are passed.

getopts(["-t", "lunch"], { array: ["t"] }) //=> { t: ["lunch"] }

The current behavior is that t's value would be the string "lunch". This would help me eliminate some boilerplate code that checks if the value is a string or array.

Way to disable built-in default values

getopts assigns default values to string (''), number (0) and boolean (false) options.

Issues:

  1. Not compatible with JS default values:
const { foo = 'bar' } = getopts(argv, { string: [ 'foo' ] });
  1. Cannot distinguish between explicit options and default values:
const { baz } = getopts(argv, { string: [ 'baz' ] });
if (baz === undefined) {
  throw new Error('--baz must be passed explicitely');
}

With modern JS syntaxes like destructuring, default values and the nullish coalescing operator (??), I don't see much use default values being handled by getopts in the future.

In the meantime, a simple option like builtInDefaults: false would be good enough to cover my use case ๐Ÿ™‚

What's your opinion on this?

ESM imports support

Would be cool to have an ESM version of getopts, so we can use named imports in Node with this module.

Support `stopEarly`

Hello,

It would be nice to support a stopEarly option, similar to minimist which would stop parsing at the first non-option argument.

This is especially useful when defining subcommands in a CLI:

function foo (args) {
  // handle foo options
  const options = getopts(args)
}

function main () {
  const options = getopts(process.argv.slice(2), {
    stopEarly: true,
  })

  const [subcommand, subargs] = options._
  if (subcommand === 'foo') {
    return foo(subargs)
  }
}

Returning the "cooked" args

I want to use getopts in pnpm. We currently use nopt (like npm) and it returns the cooked (expanded) args

We'd need this to be returned by getopts to make the switch.

getOpts(['install', '-P'], {alias: {P: 'save-prod'}})
> {_: ['install'], 'save-prod': true, cooked: ['install', '--save-prod']}

would it be ok to have this feature in getopts or should there be some mapping outside of getopts to get the cooked args?

Add options.string

Add a new string option to string type coerce flag/options.

getopts(["-vcv"], { string: ["c"] }) //=> { v: true, c: "v" }

For comparison, here is the default behavior without { string: ["c"] }

getopts(["-vcv"]) //=> { v: [true, true], c: true }

Implemented properly this should only slow us down ever so slightly.

Use Testmatrix

Switch to a declarative testing style where tests are represented as arrays of objects. See testmatrix.

exports.default = {
  "long options": [
    {
      name: "without value (boolean)",
      argv: ["--foo"],
      expected: {
        _: [],
        foo: true
      }
    },
    // ...
  ]
}

Instead of using the imperative approach common of tape/mocha/AVA, etc.

const test = require("tape")
const getopts = require("..")

test("long options", t => {
  t.deepEqual(getopts(["--foo"]), {
    _: [],
    foo: true
  }, "without value (boolean)")
  
  // ...

  t.end()
})

Boolean "swallows" values

Boolean options are considering any value as true. For example, let's consider an application that runs scripts by default and the user can filter the script to run:

{
  alias: { scriptFile: '--script-file' }, // filter the script files to run
  boolean: [ 'run' ], // indented to apply `--no-run` to avoid running script files
  default: { run: true }
}

A user then make a mistake an type the following for filtering the script to run:

node example.js --run=my-script.js

That only gives { "_": [], "run": true }, that is, when I validate run for reporting the invalid syntax, its value is true and getopts lacks the incorrect value (my-script.js) - so I can't detect and report it. That makes the application run without the user knowing about the problem (and the filter will not be applied...).

Wouldn't be a better approach to assign the received value, in order to be possible to validate it? It would still pass an if test (as true).

Question about short options

  • -w9 results in {"w": 9}
  • Shouldn't -w=9 result in "w":9 ? It gives {"w": "=9"}
  • Is it a library option or some kind of convention?

Users usually expect that a short option behaves like a long one. Example:

  • --script-file="f1.js,f2.js" โ†’ {"script-file": "f1.js,f2.js"}
  • -F="f1.js,f2.js" โ†’ {"F": "=f1.js,f2.js"} (= is added to the content)

Users are obligated to use a space (-F "f1.js,f2.js") to have the same effect. Isn't it an inconsistency in the expected interface? I mean, wouldn't be better to detect the equal sign as a value separator for short options?

Boolean options should count as known when not in argv

If an option is not present in argv, it's unknown unless it's defined in opts.default. This behavior should be true of opts.boolean (and opts.string #35).

In other words, the following is expected:

getopts([], { boolean: ["a"] }) //=> { _:[], a:false }

...while the current actual result is { _:[] }

Add an option to remove duplicate items from the `options` array

IMHO, the output of getopts() should contain only one value per option. If one sets an alias, currently it creates two items in the array (e.g. h and help), while I think it should be only one (usually help).

I suggest to add an option to create an array with only either long or short options. If an option would not have set an alias, it would add it nevertheless.

Document supported Node versions

Currently it is very unclear which Node versions are supported. CI is executed on 12 and 14, but quick glance at a source code seems to indicate that it is likely to work on Node 6+ as well. Would it be possible to clarify that in documentation and/or package.json engine entry?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.