jorgebucaran / getopts Goto Github PK
View Code? Open in Web Editor NEWNode.js CLI options parser
License: MIT License
Node.js CLI options parser
License: MIT License
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:
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.
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
})
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 }
We added it to the API section, but not Usage.
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>
}
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 }
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.
getopts
assigns default values to string (''
), number (0
) and boolean (false
) options.
Issues:
const { foo = 'bar' } = getopts(argv, { string: [ 'foo' ] });
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?
Would be cool to have an ESM version of getopts
, so we can use named imports in Node with this module.
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)
}
}
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 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.
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 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
).
-w9
results in {"w": 9}
-w=9
result in "w":9
? It gives {"w": "=9"}
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?
Hey!
I've built a Vue.js terminal emulator and switched recently from yargs
to getops
. Now a user of my library comes up with this issue: ndabAP/vue-command#176
I thought I just propagate it and see if you can say something to it.
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 { _:[] }
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.
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.