-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
pargs.mjs
100 lines (88 loc) · 2.94 KB
/
pargs.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { parseArgs } from 'util';
import { realpathSync } from 'fs';
import groupByPolyfill from 'object.groupby/polyfill';
const groupBy = groupByPolyfill();
/** @typedef {import('util').ParseArgsConfig} ParseArgsConfig */
/** @typedef {(Error | TypeError) & { code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION' | 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE' | 'ERR_INVALID_ARG_TYPE' | 'ERR_INVALID_ARG_VALUE' | 'ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL'}} ParseArgsError */
/** @type {(e: unknown) => e is ParseArgsError} */
function isParseArgsError(e) {
return !!e
&& typeof e === 'object'
&& 'code' in e
&& (
e.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION'
|| e.code === 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE'
|| e.code === 'ERR_INVALID_ARG_TYPE'
|| e.code === 'ERR_INVALID_ARG_VALUE'
|| e.code === 'ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL'
);
}
/** @type {(helpText: string, entrypointPath: string, obj: Exclude<ParseArgsConfig, 'args'>) => ReturnType<typeof parseArgs>} */
export default function pargs(helpText, entrypointPath, obj) {
const argv = process.argv.flatMap((arg) => {
try {
const realpathedArg = realpathSync(arg);
if (
realpathedArg === process.execPath
|| realpathedArg === entrypointPath
) {
return [];
}
} catch (e) { /**/ }
return arg;
});
if ('help' in obj) {
throw new TypeError('The "help" option is reserved');
}
const bools = obj.options ? Object.entries(obj.options).filter(([key, { type }]) => type === 'boolean' && key !== 'help') : [];
const inverseBools = Object.fromEntries(bools.map(([key, value]) => [
`no-${key}`,
{ default: !value.default, type: 'boolean' },
]));
/** @type {ParseArgsConfig & { tokens: true }} */
const newObj = {
args: argv,
...obj,
options: {
...obj.options,
...inverseBools,
help: {
default: false,
type: 'boolean',
},
},
tokens: true,
};
try {
const { tokens, ...results } = parseArgs(newObj);
if ('help' in results.values && results.values.help) {
console.log(helpText);
process.exit(0);
}
/** @typedef {Extract<typeof tokens[number], { kind: 'option' }>} OptionToken */
const optionTokens = tokens.filter(/** @type {(token: typeof tokens[number]) => token is OptionToken} */ (token) => token.kind === 'option');
const passedArgs = new Set(optionTokens.map(({ name }) => name));
const groups = groupBy(passedArgs, (x) => x.replace(/^no-/, ''));
bools.forEach(([key]) => {
if ((groups[key]?.length ?? 0) > 1) {
console.log(helpText);
console.error(`Error: Arguments --${key} and --no-${key} are mutually exclusive`);
process.exit(2);
}
if (passedArgs.has(`no-${key}`)) {
// @ts-expect-error
results.values[key] = !results.values[`no-${key}`];
}
// @ts-expect-error
delete results.values[`no-${key}`];
});
return obj.tokens ? { ...results, tokens } : results;
} catch (e) {
if (isParseArgsError(e)) {
console.log(helpText);
console.error(`Error: ${e.message}`);
process.exit(1);
}
throw e;
}
}