cli-nano 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +225 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +33 -0
- package/dist/interfaces.d.ts.map +1 -0
- package/dist/interfaces.js +2 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +13 -0
- package/dist/utils.js.map +1 -0
- package/package.json +59 -0
- package/src/__tests__/parse-args.spec.ts +502 -0
- package/src/index.ts +235 -0
- package/src/interfaces.ts +34 -0
- package/src/utils.ts +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2024-present, Ghislain B.
|
|
2
|
+
https://github.com/ghiscoding/gc-utils/frameworks/release
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
a copy of this software and associated documentation files (the
|
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be
|
|
13
|
+
included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
[](https://opensource.org/licenses/MIT)
|
|
2
|
+
[](http://www.typescriptlang.org/)
|
|
3
|
+
[](https://vitest.dev/)
|
|
4
|
+
[](https://codecov.io/gh/ghiscoding/cli-nano)
|
|
5
|
+
[](https://www.npmjs.com/package/cli-nano)
|
|
6
|
+
[](https://www.npmjs.com/package/cli-nano)
|
|
7
|
+
[](https://bundlephobia.com/result?p=cli-nano)
|
|
8
|
+
|
|
9
|
+
## cli-nano
|
|
10
|
+
|
|
11
|
+
Super small custom CLI similar to `Yargs` but much smaller, it uses a similar approach to NodeJS `parseArgs()` to create command-line tool (aka CLI). It is much more complete than NodeJS `parseArgs()` since it supports Positional Arguments, negated flags and also accepts both syntax `--camelCase` and/or `--kebab-case`.
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
- Parses arguments
|
|
15
|
+
- Converts flags to camelCase
|
|
16
|
+
- Negates flags when using the `--no-` prefix
|
|
17
|
+
- Outputs version when `--version`
|
|
18
|
+
- Outputs description and supplied help text when `--help`
|
|
19
|
+
- No dependencies!
|
|
20
|
+
|
|
21
|
+
### Install
|
|
22
|
+
```sh
|
|
23
|
+
npm install cli-nano
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Usage
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
#!/usr/bin/env node
|
|
30
|
+
|
|
31
|
+
import { type Config, parseArgs } from 'cli-nano';
|
|
32
|
+
|
|
33
|
+
const config: Config = {
|
|
34
|
+
command: {
|
|
35
|
+
name: 'unicorns',
|
|
36
|
+
description: 'Show a list of unicorns',
|
|
37
|
+
positional: [
|
|
38
|
+
{
|
|
39
|
+
name: 'inputs',
|
|
40
|
+
description: 'unicorn inputs',
|
|
41
|
+
type: 'string',
|
|
42
|
+
variadic: true, // one or more inputs could be provided
|
|
43
|
+
required: true,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'output',
|
|
47
|
+
description: 'output directory',
|
|
48
|
+
type: 'string',
|
|
49
|
+
required: false,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
dryRun: {
|
|
55
|
+
alias: 'd',
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
description: 'Show what would be copied, but do not actually copy any files',
|
|
58
|
+
},
|
|
59
|
+
exclude: {
|
|
60
|
+
alias: 'e',
|
|
61
|
+
type: 'array',
|
|
62
|
+
description: 'pattern or glob to exclude (may be passed multiple times)',
|
|
63
|
+
},
|
|
64
|
+
rainbow: {
|
|
65
|
+
type: 'boolean',
|
|
66
|
+
alias: 'r',
|
|
67
|
+
description: 'Enable rainbow mode',
|
|
68
|
+
},
|
|
69
|
+
verbose: {
|
|
70
|
+
alias: 'V',
|
|
71
|
+
type: 'boolean',
|
|
72
|
+
description: 'print more information to console',
|
|
73
|
+
},
|
|
74
|
+
up: {
|
|
75
|
+
type: 'number',
|
|
76
|
+
description: 'slice a path off the bottom of the paths',
|
|
77
|
+
},
|
|
78
|
+
bar: {
|
|
79
|
+
alias: 'b',
|
|
80
|
+
required: true,
|
|
81
|
+
description: 'a required bar option',
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
version: '0.1.6',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const argv = parseArgs(config);
|
|
88
|
+
console.log(argv);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Example CLI Calls
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
# Show help
|
|
95
|
+
unicorns --help
|
|
96
|
+
|
|
97
|
+
# Show version
|
|
98
|
+
unicorns --version
|
|
99
|
+
|
|
100
|
+
# With required and optional positionals
|
|
101
|
+
unicorns file1.txt file2.txt output/ -b value
|
|
102
|
+
|
|
103
|
+
# With boolean and array options
|
|
104
|
+
unicorns file1.txt output/ --dryRun --exclude pattern1 --exclude pattern2 -b value
|
|
105
|
+
|
|
106
|
+
# With negated boolean
|
|
107
|
+
unicorns file1.txt output/ --no-dryRun -b value
|
|
108
|
+
|
|
109
|
+
# With short aliases
|
|
110
|
+
unicorns file1.txt output/ -d -e pattern1 -e pattern2 -b value
|
|
111
|
+
|
|
112
|
+
# With number option
|
|
113
|
+
unicorns file1.txt output/ --up 2 -b value
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Notes
|
|
117
|
+
|
|
118
|
+
- **Variadic positionals**: Use `variadic: true` for arguments that accept multiple values.
|
|
119
|
+
- **Required options**: Add `required: true` to enforce presence of an option.
|
|
120
|
+
- **Negated booleans**: Use `--no-flag` to set a boolean option to `false`.
|
|
121
|
+
- **Array options**: Repeat the flag to collect multiple values (e.g., `--exclude a --exclude b`).
|
|
122
|
+
- **Aliases**: Use `alias` for short flags (e.g., `-d` for `--dryRun`).
|
|
123
|
+
|
|
124
|
+
See [examples/](examples/) for more usage patterns.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAG/D,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqM7D"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { camelToKebab, kebabToCamel, padString } from './utils.js';
|
|
2
|
+
export function parseArgs(config) {
|
|
3
|
+
const { command, options, version } = config;
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const result = {};
|
|
6
|
+
// Check for duplicate aliases
|
|
7
|
+
const aliasMap = new Map();
|
|
8
|
+
for (const [key, opt] of Object.entries(options)) {
|
|
9
|
+
if (opt.alias) {
|
|
10
|
+
const optAlias = opt.alias;
|
|
11
|
+
if (aliasMap.has(optAlias)) {
|
|
12
|
+
throw new Error(`Duplicate alias detected: "${opt.alias}" used for both "${aliasMap.get(optAlias)}" and "${key}"`);
|
|
13
|
+
}
|
|
14
|
+
aliasMap.set(optAlias, key);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Handle --help and --version before anything else
|
|
18
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
19
|
+
printHelp(config);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
23
|
+
console.log(version);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
// Handle positional arguments
|
|
27
|
+
let argIndex = 0;
|
|
28
|
+
const positionals = command.positional ?? [];
|
|
29
|
+
const nonOptionArgs = [];
|
|
30
|
+
while (argIndex < args.length && !args[argIndex].startsWith('-')) {
|
|
31
|
+
nonOptionArgs.push(args[argIndex]);
|
|
32
|
+
argIndex++;
|
|
33
|
+
}
|
|
34
|
+
let nonOptionIndex = 0;
|
|
35
|
+
for (let i = 0; i < positionals.length; i++) {
|
|
36
|
+
const pos = positionals[i];
|
|
37
|
+
if (pos.variadic) {
|
|
38
|
+
const remaining = positionals.length - (i + 1);
|
|
39
|
+
const values = nonOptionArgs.slice(nonOptionIndex, nonOptionArgs.length - remaining);
|
|
40
|
+
if (pos.required && values.length === 0) {
|
|
41
|
+
const usagePositionals = positionals.map(posArg => `<${posArg.name}>`).join(' ');
|
|
42
|
+
throw new Error(`Missing required positional argument, i.e.: "${command.name} ${usagePositionals}"`);
|
|
43
|
+
}
|
|
44
|
+
result[pos.name] = values;
|
|
45
|
+
nonOptionIndex += values.length;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const value = nonOptionArgs[nonOptionIndex++];
|
|
49
|
+
if (!value) {
|
|
50
|
+
const usagePositionals = positionals.map(posArg => `<${posArg.name}>`).join(' ');
|
|
51
|
+
throw new Error(`Missing required positional argument, i.e.: "${command.name} ${usagePositionals}"`);
|
|
52
|
+
}
|
|
53
|
+
result[pos.name] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Handle options
|
|
57
|
+
// Start parsing options after all non-option args used for positionals
|
|
58
|
+
argIndex = 0;
|
|
59
|
+
const consumedArgs = new Set();
|
|
60
|
+
// Mark all nonOptionArgs indices as consumed for positionals
|
|
61
|
+
let tempNonOptionIndex = 0;
|
|
62
|
+
for (let i = 0; i < positionals.length; i++) {
|
|
63
|
+
const pos = positionals[i];
|
|
64
|
+
if (pos.variadic) {
|
|
65
|
+
const remaining = positionals.length - (i + 1);
|
|
66
|
+
const values = nonOptionArgs.slice(tempNonOptionIndex, nonOptionArgs.length - remaining);
|
|
67
|
+
for (let j = tempNonOptionIndex; j < tempNonOptionIndex + values.length; j++) {
|
|
68
|
+
consumedArgs.add(args.findIndex((a, idx) => !a.startsWith('-') && !consumedArgs.has(idx) && a === nonOptionArgs[j]));
|
|
69
|
+
}
|
|
70
|
+
tempNonOptionIndex += values.length;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const value = nonOptionArgs[tempNonOptionIndex++];
|
|
74
|
+
consumedArgs.add(args.findIndex((a, idx) => !a.startsWith('-') && !consumedArgs.has(idx) && a === value));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
while (argIndex < args.length) {
|
|
78
|
+
if (consumedArgs.has(argIndex)) {
|
|
79
|
+
argIndex++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const argOrg = args[argIndex] || '';
|
|
83
|
+
let arg = args[argIndex];
|
|
84
|
+
let option;
|
|
85
|
+
let configKey;
|
|
86
|
+
if (argOrg.startsWith('-')) {
|
|
87
|
+
if (argOrg.startsWith('--')) {
|
|
88
|
+
arg = argOrg.slice(2);
|
|
89
|
+
// Try all forms: as-is, kebab-to-camel, camel-to-kebab
|
|
90
|
+
option = options[arg] || options[kebabToCamel(arg)] || options[camelToKebab(arg).replace(/-/g, '')];
|
|
91
|
+
if (option) {
|
|
92
|
+
// Find the actual config key
|
|
93
|
+
for (const key of Object.keys(options)) {
|
|
94
|
+
if (options[key] === option) {
|
|
95
|
+
configKey = key;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!option) {
|
|
101
|
+
// Try matching aliases in all forms
|
|
102
|
+
for (const key of Object.keys(options)) {
|
|
103
|
+
const opt = options[key];
|
|
104
|
+
if (!opt.alias)
|
|
105
|
+
continue;
|
|
106
|
+
if (opt.alias.includes(arg) || opt.alias.includes(kebabToCamel(arg)) || opt.alias.includes(camelToKebab(arg))) {
|
|
107
|
+
option = opt;
|
|
108
|
+
configKey = key;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (argOrg.startsWith('-')) {
|
|
115
|
+
// alias
|
|
116
|
+
arg = argOrg.slice(1);
|
|
117
|
+
if (arg) {
|
|
118
|
+
const optionKeys = Object.keys(options);
|
|
119
|
+
for (let j = 0; j < optionKeys.length; j++) {
|
|
120
|
+
const opt = options[optionKeys[j]];
|
|
121
|
+
if (!opt.alias)
|
|
122
|
+
continue;
|
|
123
|
+
if (opt.alias === arg || opt.alias === kebabToCamel(arg) || opt.alias === camelToKebab(arg)) {
|
|
124
|
+
option = opt;
|
|
125
|
+
configKey = optionKeys[j];
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!option) {
|
|
132
|
+
// Handle negated boolean in both forms
|
|
133
|
+
const isNegated = arg.startsWith('no-');
|
|
134
|
+
const optionName = isNegated ? arg.slice(3) : arg;
|
|
135
|
+
const camelOptionName = kebabToCamel(optionName);
|
|
136
|
+
option = options[optionName] || options[camelOptionName];
|
|
137
|
+
configKey = camelOptionName in options ? camelOptionName : optionName;
|
|
138
|
+
if (option?.type === 'boolean') {
|
|
139
|
+
if (result[optionName] !== undefined || result[camelOptionName] !== undefined) {
|
|
140
|
+
throw new Error('Providing same negated and truthy argument are not allowed');
|
|
141
|
+
}
|
|
142
|
+
result[configKey] = !isNegated;
|
|
143
|
+
argIndex++;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!option || !configKey) {
|
|
148
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
149
|
+
}
|
|
150
|
+
switch (option.type || 'string') {
|
|
151
|
+
case 'boolean':
|
|
152
|
+
if (result[configKey] !== undefined) {
|
|
153
|
+
throw new Error('Providing same negated and truthy argument are not allowed');
|
|
154
|
+
}
|
|
155
|
+
result[configKey] = !argOrg.startsWith('--no-') && !argOrg.startsWith('-no-');
|
|
156
|
+
break;
|
|
157
|
+
case 'string':
|
|
158
|
+
if (args[argIndex + 1] === undefined || args[argIndex + 1].startsWith('-')) {
|
|
159
|
+
throw new Error(`Missing value for option: ${configKey}`);
|
|
160
|
+
}
|
|
161
|
+
result[configKey] = args[++argIndex];
|
|
162
|
+
break;
|
|
163
|
+
case 'number':
|
|
164
|
+
if (args[argIndex + 1] === undefined || args[argIndex + 1].startsWith('-')) {
|
|
165
|
+
throw new Error(`Missing value for option: ${configKey}`);
|
|
166
|
+
}
|
|
167
|
+
result[configKey] = Number(args[++argIndex]);
|
|
168
|
+
break;
|
|
169
|
+
case 'array': {
|
|
170
|
+
if (!result[configKey])
|
|
171
|
+
result[configKey] = [];
|
|
172
|
+
const arrayValue = args[++argIndex];
|
|
173
|
+
if (arrayValue === undefined || arrayValue.startsWith('-')) {
|
|
174
|
+
throw new Error(`Missing value for array option: ${configKey}`);
|
|
175
|
+
}
|
|
176
|
+
result[configKey].push(arrayValue);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
183
|
+
}
|
|
184
|
+
argIndex++;
|
|
185
|
+
}
|
|
186
|
+
// After all parsing, check for required options
|
|
187
|
+
Object.entries(options).forEach(([key, opt]) => {
|
|
188
|
+
if (opt.required && result[key] === undefined) {
|
|
189
|
+
const aliasStr = opt.alias ? `-${opt.alias}, ` : '';
|
|
190
|
+
throw new Error(`Missing required option: ${aliasStr}--${key}`);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
function printHelp(config) {
|
|
196
|
+
const { command, options } = config;
|
|
197
|
+
// Build usage string for positionals
|
|
198
|
+
const usagePositionals = (command.positional ?? [])
|
|
199
|
+
.map(p => {
|
|
200
|
+
const variadic = p.variadic ? '..' : '';
|
|
201
|
+
if (p.required) {
|
|
202
|
+
return `<${p.name}${variadic}>`;
|
|
203
|
+
}
|
|
204
|
+
return `[${p.name}${variadic}]`;
|
|
205
|
+
})
|
|
206
|
+
.join(' ');
|
|
207
|
+
console.log('Usage:');
|
|
208
|
+
console.log(` ${command.name} ${usagePositionals} [options] ${command.description}`);
|
|
209
|
+
console.log('\nPositionals:');
|
|
210
|
+
command.positional?.forEach(arg => {
|
|
211
|
+
console.log(` ${arg.name.padEnd(20)}${arg.description.slice(0, 65).padEnd(65)}[${arg.type || 'string'}]`);
|
|
212
|
+
});
|
|
213
|
+
console.log('\nOptions:');
|
|
214
|
+
Object.keys(options).forEach(key => {
|
|
215
|
+
const option = options[key];
|
|
216
|
+
const requiredStr = option.required ? '[required]' : '';
|
|
217
|
+
const aliasStr = option.alias ? `-${option.alias}, ` : '';
|
|
218
|
+
console.log(` ${aliasStr.padEnd(4)}--${key.padEnd(14)}${(option.description || '').slice(0, 65).padEnd(65)}[${option.type || 'string'}]${requiredStr}`);
|
|
219
|
+
});
|
|
220
|
+
console.log('\nDefault options:');
|
|
221
|
+
console.log(`${padString(' -h, --help', 21)} ${padString('Show help', 64)} [boolean]`);
|
|
222
|
+
console.log(`${padString(' -v, --version', 21)} ${padString('Show version number', 64)} [boolean]`);
|
|
223
|
+
console.log('\n');
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ArgumentOptions {
|
|
2
|
+
/** command option type */
|
|
3
|
+
type?: 'string' | 'boolean' | 'number' | 'array';
|
|
4
|
+
/** description of the command option */
|
|
5
|
+
description: string;
|
|
6
|
+
/** defaults to undefined, provide shorter aliases as command options */
|
|
7
|
+
alias?: string | string[];
|
|
8
|
+
/** defaults to false, is the option required? */
|
|
9
|
+
required?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface CommandOptions {
|
|
12
|
+
/** CLI command name, used in the help docs */
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
positional?: {
|
|
16
|
+
/** positional argument name (it will be displayed in the help docs) */
|
|
17
|
+
name: string;
|
|
18
|
+
/** positional argument description */
|
|
19
|
+
description: string;
|
|
20
|
+
/** postional argument type */
|
|
21
|
+
type?: 'string';
|
|
22
|
+
/** defaults to false, allows multiple values for this positional argument */
|
|
23
|
+
variadic?: boolean;
|
|
24
|
+
/** defaults to false, is the positional argument required? */
|
|
25
|
+
required?: boolean;
|
|
26
|
+
}[];
|
|
27
|
+
}
|
|
28
|
+
export interface Config {
|
|
29
|
+
command: CommandOptions;
|
|
30
|
+
options: Record<string, ArgumentOptions>;
|
|
31
|
+
version: string;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=interfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,0BAA0B;IAC1B,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IACjD,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE;QACX,uEAAuE;QACvE,IAAI,EAAE,MAAM,CAAC;QACb,sCAAsC;QACtC,WAAW,EAAE,MAAM,CAAC;QACpB,8BAA8B;QAC9B,IAAI,CAAC,EAAE,QAAQ,CAAC;QAChB,6EAA6E;QAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,8DAA8D;QAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,EAAE,CAAC;CACL;AAED,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE,cAAc,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":""}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Utility to convert kebab-case to camelCase */
|
|
2
|
+
export declare function kebabToCamel(str: string): string;
|
|
3
|
+
/** Utility to convert camelCase to kebab-case */
|
|
4
|
+
export declare function camelToKebab(str: string): string;
|
|
5
|
+
/** add whitespace padding to any input string */
|
|
6
|
+
export declare function padString(input: string, padding: number): string;
|
|
7
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,UAEvC;AAED,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,UAEvC;AAED,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAEvD"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Utility to convert kebab-case to camelCase */
|
|
2
|
+
export function kebabToCamel(str) {
|
|
3
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
4
|
+
}
|
|
5
|
+
/** Utility to convert camelCase to kebab-case */
|
|
6
|
+
export function camelToKebab(str) {
|
|
7
|
+
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
/** add whitespace padding to any input string */
|
|
10
|
+
export function padString(input, padding) {
|
|
11
|
+
return input.padEnd(padding);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,GAAG,CAAC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AAClE,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,OAAe;IACtD,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cli-nano",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Mini command-line tool similar to `yargs` or `parseArgs` from Node.js that accepts positional arguments, flags and options.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"/dist",
|
|
20
|
+
"/src"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Ghislain B.",
|
|
24
|
+
"homepage": "https://github.com/ghiscoding/cli-nano",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/ghiscoding/cli-nano.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/ghiscoding/cli-nano/issues"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"clean": "premove dist",
|
|
34
|
+
"build": "npm run clean && npm run biome:write && tsc",
|
|
35
|
+
"biome:check": "npm run biome:lint:check && npm run biome:format:check",
|
|
36
|
+
"biome:write": "npm run biome:lint:write && npm run biome:format:write",
|
|
37
|
+
"biome:lint:check": "biome lint ./src",
|
|
38
|
+
"biome:lint:write": "biome lint --write ./src",
|
|
39
|
+
"biome:format:check": "biome format ./src",
|
|
40
|
+
"biome:format:write": "biome format --write ./src",
|
|
41
|
+
"preview:release": "release-it --only-version --dry-run",
|
|
42
|
+
"release": "release-it --only-version",
|
|
43
|
+
"test": "vitest --watch --config ./vitest.config.mts",
|
|
44
|
+
"test:coverage": "vitest --coverage --config ./vitest.config.mts"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@biomejs/biome": "^2.0.5",
|
|
48
|
+
"@release-it/conventional-changelog": "^10.0.1",
|
|
49
|
+
"@types/node": "^22.15.33",
|
|
50
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
51
|
+
"premove": "^4.0.0",
|
|
52
|
+
"release-it": "^19.0.3",
|
|
53
|
+
"typescript": "^5.8.3",
|
|
54
|
+
"vitest": "^3.2.4"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20.0.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from '../index.js';
|
|
4
|
+
import type { Config } from '../interfaces.js';
|
|
5
|
+
|
|
6
|
+
const config: Config = {
|
|
7
|
+
command: {
|
|
8
|
+
name: 'copyfiles',
|
|
9
|
+
description: 'Copy files from a source to a destination directory',
|
|
10
|
+
positional: [
|
|
11
|
+
{
|
|
12
|
+
name: 'inFile',
|
|
13
|
+
description: 'source files',
|
|
14
|
+
type: 'string',
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'outDirectory',
|
|
19
|
+
description: 'destination directory',
|
|
20
|
+
required: true,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
options: {
|
|
25
|
+
all: {
|
|
26
|
+
alias: 'a',
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
description: 'include files & directories begining with a dot (.)',
|
|
29
|
+
},
|
|
30
|
+
dryRun: {
|
|
31
|
+
alias: 'd',
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
description: 'Show what would be copied, but do not actually copy any files',
|
|
34
|
+
},
|
|
35
|
+
exclude: {
|
|
36
|
+
alias: 'e',
|
|
37
|
+
type: 'array',
|
|
38
|
+
description: 'pattern or glob to exclude (may be passed multiple times)',
|
|
39
|
+
},
|
|
40
|
+
up: {
|
|
41
|
+
type: 'number',
|
|
42
|
+
description: 'slice a path off the bottom of the paths',
|
|
43
|
+
},
|
|
44
|
+
bar: {
|
|
45
|
+
alias: 'b',
|
|
46
|
+
required: true,
|
|
47
|
+
description: 'a required bar option',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
version: '0.1.6',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe('parseArgs', () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...[]]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
vi.restoreAllMocks();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should parse positional arguments correctly', () => {
|
|
63
|
+
const args = ['file1.txt', 'output/', '--bar', 'value'];
|
|
64
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
65
|
+
|
|
66
|
+
const result = parseArgs(config);
|
|
67
|
+
|
|
68
|
+
expect(result.inFile).toBe('file1.txt');
|
|
69
|
+
expect(result.outDirectory).toBe('output/');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should parse camelCase boolean options correctly', () => {
|
|
73
|
+
const args = ['file1.txt', 'output/', '--all', '--no-dryRun', '--bar', 'value'];
|
|
74
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
75
|
+
|
|
76
|
+
const result = parseArgs(config);
|
|
77
|
+
|
|
78
|
+
expect(result.inFile).toBe('file1.txt');
|
|
79
|
+
expect(result.outDirectory).toBe('output/');
|
|
80
|
+
expect(result.all).toBe(true);
|
|
81
|
+
expect(result.dryRun).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should parse kebab-case boolean options correctly', () => {
|
|
85
|
+
const args = ['file1.txt', 'output/', '--all', '--no-dry-run', '--bar', 'value'];
|
|
86
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
87
|
+
|
|
88
|
+
const result = parseArgs(config);
|
|
89
|
+
|
|
90
|
+
expect(result.inFile).toBe('file1.txt');
|
|
91
|
+
expect(result.outDirectory).toBe('output/');
|
|
92
|
+
expect(result.all).toBe(true);
|
|
93
|
+
expect(result.dryRun).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should parse string options correctly', () => {
|
|
97
|
+
const args = ['file1.txt', 'output/', '--up', '2', '--bar', 'value'];
|
|
98
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
99
|
+
const result = parseArgs(config);
|
|
100
|
+
expect(result.up).toBe(2);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should parse string options correctly with an alias', () => {
|
|
104
|
+
const args = ['file1.txt', 'output/', '--up', '2', '-b', 'value'];
|
|
105
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
106
|
+
const result = parseArgs(config);
|
|
107
|
+
expect(result.up).toBe(2);
|
|
108
|
+
expect(result.bar).toBe('value');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should parse array options correctly when defined at the end of the command options', () => {
|
|
112
|
+
const args = ['file1.txt', 'output/', '-b', 'value', '--exclude', 'pattern1', '--exclude', 'pattern2'];
|
|
113
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
114
|
+
const result = parseArgs(config);
|
|
115
|
+
expect(result.exclude).toEqual(['pattern1', 'pattern2']);
|
|
116
|
+
expect(result.bar).toBe('value');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should parse array options correctly when defined in the middle of the command options with camelCase arguments', () => {
|
|
120
|
+
const args = ['file1.txt', 'output/', '--exclude', 'pattern1', '--exclude', 'pattern2', '-b', 'value', '--dryRun'];
|
|
121
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
122
|
+
const result = parseArgs(config);
|
|
123
|
+
expect(result.exclude).toEqual(['pattern1', 'pattern2']);
|
|
124
|
+
expect(result.dryRun).toBe(true);
|
|
125
|
+
expect(result.bar).toBe('value');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should parse array options correctly when defined in the middle of the command options with kebab-case arguments', () => {
|
|
129
|
+
const args = ['file1.txt', 'output/', '--exclude', 'pattern1', '--exclude', 'pattern2', '-b', 'value', '--dry-run'];
|
|
130
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
131
|
+
const result = parseArgs(config);
|
|
132
|
+
expect(result.exclude).toEqual(['pattern1', 'pattern2']);
|
|
133
|
+
expect(result.dryRun).toBe(true);
|
|
134
|
+
expect(result.bar).toBe('value');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should throw an error for unknown options', () => {
|
|
138
|
+
const args = ['file1.txt', 'output/', '-b', 'value', '--unknown'];
|
|
139
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
140
|
+
expect(() => parseArgs(config)).toThrowError('Unknown option: unknown');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should throw an error for unknown kebab-case options', () => {
|
|
144
|
+
const args = ['file1.txt', 'output/', '-b', 'value', '--unknown-kebab'];
|
|
145
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
146
|
+
expect(() => parseArgs(config)).toThrowError('Unknown option: unknown-kebab');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should throw an error for unknown camelCase options', () => {
|
|
150
|
+
const args = ['file1.txt', 'output/', '-b', 'value', '--unknownCamel'];
|
|
151
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
152
|
+
expect(() => parseArgs(config)).toThrowError('Unknown option: unknownCamel');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should throw when truthy and --no camelCase prefix arguments are both provided', () => {
|
|
156
|
+
const args = ['file1.txt', 'output/', '--all', '--dryRun', '--no-dryRun', '-b', 'value'];
|
|
157
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
158
|
+
expect(() => parseArgs(config)).toThrowError('Providing same negated and truthy argument are not allowed');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should throw when truthy and --no kebab-case prefix arguments are both provided', () => {
|
|
162
|
+
const args = ['file1.txt', 'output/', '--all', '--dryRun', '--no-dry-run', '-b', 'value'];
|
|
163
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
164
|
+
expect(() => parseArgs(config)).toThrowError('Providing same negated and truthy argument are not allowed');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should throw when --no prefix and truthy arguments are both provided', () => {
|
|
168
|
+
const args = ['file1.txt', 'output/', '--all', '--no-dryRun', '--dryRun', '-b', 'value'];
|
|
169
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
170
|
+
expect(() => parseArgs(config)).toThrowError('Providing same negated and truthy argument are not allowed');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should throw when positional arguments are missing', () => {
|
|
174
|
+
const args = ['file1.txt', '--all', '--dryRun'];
|
|
175
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
176
|
+
|
|
177
|
+
expect(() => parseArgs(config)).toThrow('Missing required positional argument, i.e.: "copyfiles <inFile> <outDirectory>');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should throw when positional arguments are missing and it will not try to read "value" as positional argument either', () => {
|
|
181
|
+
const args = ['file1.txt', '--all', '--dryRun', '-b', 'value'];
|
|
182
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
183
|
+
|
|
184
|
+
expect(() => parseArgs(config)).toThrow('Missing required positional argument, i.e.: "copyfiles <inFile> <outDirectory>');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should throw when required options are missing', () => {
|
|
188
|
+
const args = ['file1.txt', 'output', '--all', '--dryRun'];
|
|
189
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
190
|
+
|
|
191
|
+
expect(() => parseArgs(config)).toThrow('Missing required option: -b, --bar');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should throw if the same alias is defined for multiple options', () => {
|
|
195
|
+
const configWithDupAlias: Config = {
|
|
196
|
+
...config,
|
|
197
|
+
options: {
|
|
198
|
+
foo: { alias: 'x', type: 'boolean', description: '' },
|
|
199
|
+
bar: { alias: 'x', type: 'boolean', description: '' },
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
expect(() => parseArgs(configWithDupAlias)).toThrow('Duplicate alias detected: "x" used for both "foo" and "bar"');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle help command', () =>
|
|
206
|
+
new Promise((done: any) => {
|
|
207
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
208
|
+
const args = ['--help'];
|
|
209
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
210
|
+
try {
|
|
211
|
+
parseArgs(config);
|
|
212
|
+
} catch (error: any) {
|
|
213
|
+
expect(error.message).toBe('process.exit unexpectedly called with "0"');
|
|
214
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Usage:'));
|
|
215
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
216
|
+
expect.stringContaining('copyfiles <inFile> <outDirectory> [options] Copy files from a source to a destination directory'),
|
|
217
|
+
);
|
|
218
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('\nPositionals:'));
|
|
219
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
220
|
+
expect.stringContaining(' inFile source files [string]'),
|
|
221
|
+
);
|
|
222
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
223
|
+
expect.stringContaining(' -d, --dryRun Show what would be copied, but do not actually copy any files [boolean]'),
|
|
224
|
+
);
|
|
225
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
226
|
+
expect.stringContaining(
|
|
227
|
+
' -b, --bar a required bar option [string][required]',
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('\nDefault options:'));
|
|
231
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
232
|
+
expect.stringContaining(' -h, --help Show help [boolean]'),
|
|
233
|
+
);
|
|
234
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
235
|
+
expect.stringContaining(' -v, --version Show version number [boolean]'),
|
|
236
|
+
);
|
|
237
|
+
done();
|
|
238
|
+
}
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
it('should handle version command', () =>
|
|
242
|
+
new Promise((done: any) => {
|
|
243
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
244
|
+
const args = ['--version'];
|
|
245
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
246
|
+
try {
|
|
247
|
+
parseArgs(config);
|
|
248
|
+
} catch (error: any) {
|
|
249
|
+
expect(error.message).toBe('process.exit unexpectedly called with "0"');
|
|
250
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('0.1.6'));
|
|
251
|
+
done();
|
|
252
|
+
}
|
|
253
|
+
}));
|
|
254
|
+
|
|
255
|
+
it('should parse optional variadic positional arguments (zero or more)', () => {
|
|
256
|
+
const config: Config = {
|
|
257
|
+
command: {
|
|
258
|
+
name: 'test',
|
|
259
|
+
description: 'Test optional variadic',
|
|
260
|
+
positional: [
|
|
261
|
+
{
|
|
262
|
+
name: 'inputs',
|
|
263
|
+
description: 'input files',
|
|
264
|
+
type: 'string',
|
|
265
|
+
variadic: true,
|
|
266
|
+
required: false,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'outDir',
|
|
270
|
+
description: 'output directory',
|
|
271
|
+
required: true,
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
options: {},
|
|
276
|
+
version: '1.0.0',
|
|
277
|
+
};
|
|
278
|
+
// No inputs
|
|
279
|
+
let args = ['dist'];
|
|
280
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
281
|
+
let result = parseArgs(config);
|
|
282
|
+
expect(result.inputs).toEqual([]);
|
|
283
|
+
expect(result.outDir).toBe('dist');
|
|
284
|
+
|
|
285
|
+
// Multiple inputs
|
|
286
|
+
args = ['file1', 'file2', 'dist'];
|
|
287
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
288
|
+
result = parseArgs(config);
|
|
289
|
+
expect(result.inputs).toEqual(['file1', 'file2']);
|
|
290
|
+
expect(result.outDir).toBe('dist');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should parse a single optional variadic positional arguments (zero or more)', () => {
|
|
294
|
+
const config: Config = {
|
|
295
|
+
command: {
|
|
296
|
+
name: 'test',
|
|
297
|
+
description: 'Test optional variadic',
|
|
298
|
+
positional: [
|
|
299
|
+
{
|
|
300
|
+
name: 'inputs',
|
|
301
|
+
description: 'input files',
|
|
302
|
+
type: 'string',
|
|
303
|
+
variadic: true,
|
|
304
|
+
required: true,
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
options: {},
|
|
309
|
+
version: '1.0.0',
|
|
310
|
+
};
|
|
311
|
+
// No inputs
|
|
312
|
+
let args: string[] = [];
|
|
313
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
314
|
+
expect(() => parseArgs(config)).toThrow('Missing required positional argument, i.e.: "test <inputs>');
|
|
315
|
+
|
|
316
|
+
// Multiple inputs
|
|
317
|
+
args = ['file1', 'file2'];
|
|
318
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
319
|
+
const result = parseArgs(config);
|
|
320
|
+
expect(result.inputs).toEqual(['file1', 'file2']);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should print usage with required variadic positional argument using <inFile..>', () => {
|
|
324
|
+
const configWithVariadic: Config = {
|
|
325
|
+
...config,
|
|
326
|
+
command: {
|
|
327
|
+
...config.command,
|
|
328
|
+
positional: [
|
|
329
|
+
{
|
|
330
|
+
name: 'inFile',
|
|
331
|
+
description: 'source files',
|
|
332
|
+
type: 'string',
|
|
333
|
+
variadic: true,
|
|
334
|
+
required: true,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'outDirectory',
|
|
338
|
+
description: 'destination directory',
|
|
339
|
+
required: true,
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
345
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', '--help']);
|
|
346
|
+
try {
|
|
347
|
+
parseArgs(configWithVariadic);
|
|
348
|
+
} catch {}
|
|
349
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('<inFile..>'));
|
|
350
|
+
spy.mockRestore();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should print usage without any positional argument defined', () => {
|
|
354
|
+
const configWithoutPositional: Config = {
|
|
355
|
+
...config,
|
|
356
|
+
command: {
|
|
357
|
+
...config.command,
|
|
358
|
+
},
|
|
359
|
+
options: {
|
|
360
|
+
file: {
|
|
361
|
+
description: 'source files',
|
|
362
|
+
type: 'string',
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
367
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', '--help']);
|
|
368
|
+
try {
|
|
369
|
+
parseArgs(configWithoutPositional);
|
|
370
|
+
} catch {}
|
|
371
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('--file'));
|
|
372
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('source files'));
|
|
373
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('[string]'));
|
|
374
|
+
spy.mockRestore();
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should throw if required boolean flag is missing', () => {
|
|
378
|
+
const config: Config = {
|
|
379
|
+
command: {
|
|
380
|
+
name: 'test',
|
|
381
|
+
description: 'Test required flag',
|
|
382
|
+
},
|
|
383
|
+
options: {
|
|
384
|
+
force: {
|
|
385
|
+
alias: 'f',
|
|
386
|
+
type: 'boolean',
|
|
387
|
+
required: true,
|
|
388
|
+
description: 'Force operation',
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
version: '1.0.0',
|
|
392
|
+
};
|
|
393
|
+
const args: string[] = [];
|
|
394
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
395
|
+
expect(() => parseArgs(config)).toThrow('Missing required option: -f, --force');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should parse kebab-case long option when alias is camelCase', () => {
|
|
399
|
+
const configWithCamelAlias: Config = {
|
|
400
|
+
...config,
|
|
401
|
+
options: {
|
|
402
|
+
...config.options,
|
|
403
|
+
testOption: {
|
|
404
|
+
alias: 'testAliasCamel',
|
|
405
|
+
type: 'boolean',
|
|
406
|
+
description: 'A test option with camelCase alias',
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
const args = ['file1.txt', 'output/', '--test-alias-camel', '-b', 'value'];
|
|
411
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
412
|
+
const result = parseArgs(configWithCamelAlias);
|
|
413
|
+
expect(result.testOption).toBe(true);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('should parse camelCase long option when alias is kebab-case', () => {
|
|
417
|
+
const configWithKebabAlias: Config = {
|
|
418
|
+
...config,
|
|
419
|
+
options: {
|
|
420
|
+
...config.options,
|
|
421
|
+
testOption: {
|
|
422
|
+
alias: 'test-alias-kebab',
|
|
423
|
+
type: 'boolean',
|
|
424
|
+
description: 'A test option with kebab-case alias',
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
const args = ['file1.txt', 'output/', '--testAliasKebab', '-b', 'value'];
|
|
429
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
430
|
+
const result = parseArgs(configWithKebabAlias);
|
|
431
|
+
expect(result.testOption).toBe(true);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('should throw if too many positional arguments are provided', () => {
|
|
435
|
+
const args = ['file1.txt', 'output/', 'extra.txt', '-b', 'value'];
|
|
436
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
437
|
+
expect(() => parseArgs(config)).toThrow('Unknown argument: extra.txt');
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should throw if array option is missing a value', () => {
|
|
441
|
+
const args = ['file1.txt', 'output/', '--exclude', '-b', 'value'];
|
|
442
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
443
|
+
expect(() => parseArgs(config)).toThrow('Missing value for array option: exclude');
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('should throw if string option is missing a value', () => {
|
|
447
|
+
const args = ['file1.txt', 'output/', '--bar'];
|
|
448
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
449
|
+
expect(() => parseArgs(config)).toThrow('Missing value for option: bar');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should throw if number option is missing a value', () => {
|
|
453
|
+
const args = ['file1.txt', 'output/', '--up'];
|
|
454
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
455
|
+
expect(() => parseArgs(config)).toThrow('Missing value for option: up');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('should throw unknown option when short flag does not match any alias', () => {
|
|
459
|
+
const configWithNoAlias: Config = {
|
|
460
|
+
...config,
|
|
461
|
+
options: {
|
|
462
|
+
...config.options,
|
|
463
|
+
noAliasOpt: {
|
|
464
|
+
type: 'boolean',
|
|
465
|
+
description: 'Option with no alias',
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
const args = ['file1.txt', 'output/', '-x', '-b', 'value'];
|
|
470
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', ...args]);
|
|
471
|
+
expect(() => parseArgs(configWithNoAlias)).toThrow('Unknown option: x');
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('should print usage with optional positional argument', () => {
|
|
475
|
+
const configWithOptional: Config = {
|
|
476
|
+
...config,
|
|
477
|
+
command: {
|
|
478
|
+
...config.command,
|
|
479
|
+
positional: [
|
|
480
|
+
{
|
|
481
|
+
name: 'inFile',
|
|
482
|
+
description: 'source files',
|
|
483
|
+
type: 'string',
|
|
484
|
+
required: false, // <-- optional positional
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: 'outDirectory',
|
|
488
|
+
description: 'destination directory',
|
|
489
|
+
required: true,
|
|
490
|
+
},
|
|
491
|
+
],
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
495
|
+
vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'cli.js', '--help']);
|
|
496
|
+
try {
|
|
497
|
+
parseArgs(configWithOptional);
|
|
498
|
+
} catch {}
|
|
499
|
+
expect(spy).toHaveBeenCalledWith(expect.stringContaining('[inFile]'));
|
|
500
|
+
spy.mockRestore();
|
|
501
|
+
});
|
|
502
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { ArgumentOptions, Config } from './interfaces.js';
|
|
2
|
+
import { camelToKebab, kebabToCamel, padString } from './utils.js';
|
|
3
|
+
|
|
4
|
+
export function parseArgs(config: Config): Record<string, any> {
|
|
5
|
+
const { command, options, version } = config;
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const result: Record<string, any> = {};
|
|
8
|
+
|
|
9
|
+
// Check for duplicate aliases
|
|
10
|
+
const aliasMap = new Map<string, string>();
|
|
11
|
+
for (const [key, opt] of Object.entries(options)) {
|
|
12
|
+
if (opt.alias) {
|
|
13
|
+
const optAlias = opt.alias as string;
|
|
14
|
+
if (aliasMap.has(optAlias)) {
|
|
15
|
+
throw new Error(`Duplicate alias detected: "${opt.alias}" used for both "${aliasMap.get(optAlias)}" and "${key}"`);
|
|
16
|
+
}
|
|
17
|
+
aliasMap.set(optAlias, key);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Handle --help and --version before anything else
|
|
22
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
23
|
+
printHelp(config);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
27
|
+
console.log(version);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle positional arguments
|
|
32
|
+
let argIndex = 0;
|
|
33
|
+
const positionals = command.positional ?? [];
|
|
34
|
+
const nonOptionArgs: string[] = [];
|
|
35
|
+
while (argIndex < args.length && !args[argIndex].startsWith('-')) {
|
|
36
|
+
nonOptionArgs.push(args[argIndex]);
|
|
37
|
+
argIndex++;
|
|
38
|
+
}
|
|
39
|
+
let nonOptionIndex = 0;
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < positionals.length; i++) {
|
|
42
|
+
const pos = positionals[i];
|
|
43
|
+
if (pos.variadic) {
|
|
44
|
+
const remaining = positionals.length - (i + 1);
|
|
45
|
+
const values = nonOptionArgs.slice(nonOptionIndex, nonOptionArgs.length - remaining);
|
|
46
|
+
if (pos.required && values.length === 0) {
|
|
47
|
+
const usagePositionals = positionals.map(posArg => `<${posArg.name}>`).join(' ');
|
|
48
|
+
throw new Error(`Missing required positional argument, i.e.: "${command.name} ${usagePositionals}"`);
|
|
49
|
+
}
|
|
50
|
+
result[pos.name] = values;
|
|
51
|
+
nonOptionIndex += values.length;
|
|
52
|
+
} else {
|
|
53
|
+
const value = nonOptionArgs[nonOptionIndex++];
|
|
54
|
+
if (!value) {
|
|
55
|
+
const usagePositionals = positionals.map(posArg => `<${posArg.name}>`).join(' ');
|
|
56
|
+
throw new Error(`Missing required positional argument, i.e.: "${command.name} ${usagePositionals}"`);
|
|
57
|
+
}
|
|
58
|
+
result[pos.name] = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle options
|
|
63
|
+
// Start parsing options after all non-option args used for positionals
|
|
64
|
+
argIndex = 0;
|
|
65
|
+
const consumedArgs = new Set<number>();
|
|
66
|
+
// Mark all nonOptionArgs indices as consumed for positionals
|
|
67
|
+
let tempNonOptionIndex = 0;
|
|
68
|
+
for (let i = 0; i < positionals.length; i++) {
|
|
69
|
+
const pos = positionals[i];
|
|
70
|
+
if (pos.variadic) {
|
|
71
|
+
const remaining = positionals.length - (i + 1);
|
|
72
|
+
const values = nonOptionArgs.slice(tempNonOptionIndex, nonOptionArgs.length - remaining);
|
|
73
|
+
for (let j = tempNonOptionIndex; j < tempNonOptionIndex + values.length; j++) {
|
|
74
|
+
consumedArgs.add(args.findIndex((a, idx) => !a.startsWith('-') && !consumedArgs.has(idx) && a === nonOptionArgs[j]));
|
|
75
|
+
}
|
|
76
|
+
tempNonOptionIndex += values.length;
|
|
77
|
+
} else {
|
|
78
|
+
const value = nonOptionArgs[tempNonOptionIndex++];
|
|
79
|
+
consumedArgs.add(args.findIndex((a, idx) => !a.startsWith('-') && !consumedArgs.has(idx) && a === value));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
while (argIndex < args.length) {
|
|
84
|
+
if (consumedArgs.has(argIndex)) {
|
|
85
|
+
argIndex++;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const argOrg = args[argIndex] || '';
|
|
89
|
+
let arg = args[argIndex];
|
|
90
|
+
let option: ArgumentOptions | undefined;
|
|
91
|
+
let configKey: string | undefined;
|
|
92
|
+
|
|
93
|
+
if (argOrg.startsWith('-')) {
|
|
94
|
+
if (argOrg.startsWith('--')) {
|
|
95
|
+
arg = argOrg.slice(2);
|
|
96
|
+
// Try all forms: as-is, kebab-to-camel, camel-to-kebab
|
|
97
|
+
option = options[arg] || options[kebabToCamel(arg)] || options[camelToKebab(arg).replace(/-/g, '')];
|
|
98
|
+
if (option) {
|
|
99
|
+
// Find the actual config key
|
|
100
|
+
for (const key of Object.keys(options)) {
|
|
101
|
+
if (options[key] === option) {
|
|
102
|
+
configKey = key;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!option) {
|
|
108
|
+
// Try matching aliases in all forms
|
|
109
|
+
for (const key of Object.keys(options)) {
|
|
110
|
+
const opt = options[key];
|
|
111
|
+
if (!opt.alias) continue;
|
|
112
|
+
if (opt.alias.includes(arg) || opt.alias.includes(kebabToCamel(arg)) || opt.alias.includes(camelToKebab(arg))) {
|
|
113
|
+
option = opt;
|
|
114
|
+
configKey = key;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} else if (argOrg.startsWith('-')) {
|
|
120
|
+
// alias
|
|
121
|
+
arg = argOrg.slice(1);
|
|
122
|
+
if (arg) {
|
|
123
|
+
const optionKeys = Object.keys(options);
|
|
124
|
+
for (let j = 0; j < optionKeys.length; j++) {
|
|
125
|
+
const opt = options[optionKeys[j]];
|
|
126
|
+
if (!opt.alias) continue;
|
|
127
|
+
if (opt.alias === arg || opt.alias === kebabToCamel(arg) || opt.alias === camelToKebab(arg)) {
|
|
128
|
+
option = opt;
|
|
129
|
+
configKey = optionKeys[j];
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!option) {
|
|
137
|
+
// Handle negated boolean in both forms
|
|
138
|
+
const isNegated = arg.startsWith('no-');
|
|
139
|
+
const optionName = isNegated ? arg.slice(3) : arg;
|
|
140
|
+
const camelOptionName = kebabToCamel(optionName);
|
|
141
|
+
option = options[optionName] || options[camelOptionName];
|
|
142
|
+
configKey = camelOptionName in options ? camelOptionName : optionName;
|
|
143
|
+
if (option?.type === 'boolean') {
|
|
144
|
+
if (result[optionName] !== undefined || result[camelOptionName] !== undefined) {
|
|
145
|
+
throw new Error('Providing same negated and truthy argument are not allowed');
|
|
146
|
+
}
|
|
147
|
+
result[configKey] = !isNegated;
|
|
148
|
+
argIndex++;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!option || !configKey) {
|
|
154
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
switch (option.type || 'string') {
|
|
158
|
+
case 'boolean':
|
|
159
|
+
if (result[configKey] !== undefined) {
|
|
160
|
+
throw new Error('Providing same negated and truthy argument are not allowed');
|
|
161
|
+
}
|
|
162
|
+
result[configKey] = !argOrg.startsWith('--no-') && !argOrg.startsWith('-no-');
|
|
163
|
+
break;
|
|
164
|
+
case 'string':
|
|
165
|
+
if (args[argIndex + 1] === undefined || args[argIndex + 1].startsWith('-')) {
|
|
166
|
+
throw new Error(`Missing value for option: ${configKey}`);
|
|
167
|
+
}
|
|
168
|
+
result[configKey] = args[++argIndex];
|
|
169
|
+
break;
|
|
170
|
+
case 'number':
|
|
171
|
+
if (args[argIndex + 1] === undefined || args[argIndex + 1].startsWith('-')) {
|
|
172
|
+
throw new Error(`Missing value for option: ${configKey}`);
|
|
173
|
+
}
|
|
174
|
+
result[configKey] = Number(args[++argIndex]);
|
|
175
|
+
break;
|
|
176
|
+
case 'array': {
|
|
177
|
+
if (!result[configKey]) result[configKey] = [];
|
|
178
|
+
const arrayValue = args[++argIndex];
|
|
179
|
+
if (arrayValue === undefined || arrayValue.startsWith('-')) {
|
|
180
|
+
throw new Error(`Missing value for array option: ${configKey}`);
|
|
181
|
+
}
|
|
182
|
+
result[configKey].push(arrayValue);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
188
|
+
}
|
|
189
|
+
argIndex++;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// After all parsing, check for required options
|
|
193
|
+
Object.entries(options).forEach(([key, opt]) => {
|
|
194
|
+
if (opt.required && result[key] === undefined) {
|
|
195
|
+
const aliasStr = opt.alias ? `-${opt.alias}, ` : '';
|
|
196
|
+
throw new Error(`Missing required option: ${aliasStr}--${key}`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function printHelp(config: Config) {
|
|
204
|
+
const { command, options } = config;
|
|
205
|
+
|
|
206
|
+
// Build usage string for positionals
|
|
207
|
+
const usagePositionals = (command.positional ?? [])
|
|
208
|
+
.map(p => {
|
|
209
|
+
const variadic = p.variadic ? '..' : '';
|
|
210
|
+
if (p.required) {
|
|
211
|
+
return `<${p.name}${variadic}>`;
|
|
212
|
+
}
|
|
213
|
+
return `[${p.name}${variadic}]`;
|
|
214
|
+
})
|
|
215
|
+
.join(' ');
|
|
216
|
+
console.log('Usage:');
|
|
217
|
+
console.log(` ${command.name} ${usagePositionals} [options] ${command.description}`);
|
|
218
|
+
console.log('\nPositionals:');
|
|
219
|
+
command.positional?.forEach(arg => {
|
|
220
|
+
console.log(` ${arg.name.padEnd(20)}${arg.description.slice(0, 65).padEnd(65)}[${arg.type || 'string'}]`);
|
|
221
|
+
});
|
|
222
|
+
console.log('\nOptions:');
|
|
223
|
+
Object.keys(options).forEach(key => {
|
|
224
|
+
const option = options[key];
|
|
225
|
+
const requiredStr = option.required ? '[required]' : '';
|
|
226
|
+
const aliasStr = option.alias ? `-${option.alias}, ` : '';
|
|
227
|
+
console.log(
|
|
228
|
+
` ${aliasStr.padEnd(4)}--${key.padEnd(14)}${(option.description || '').slice(0, 65).padEnd(65)}[${option.type || 'string'}]${requiredStr}`,
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
console.log('\nDefault options:');
|
|
232
|
+
console.log(`${padString(' -h, --help', 21)} ${padString('Show help', 64)} [boolean]`);
|
|
233
|
+
console.log(`${padString(' -v, --version', 21)} ${padString('Show version number', 64)} [boolean]`);
|
|
234
|
+
console.log('\n');
|
|
235
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface ArgumentOptions {
|
|
2
|
+
/** command option type */
|
|
3
|
+
type?: 'string' | 'boolean' | 'number' | 'array';
|
|
4
|
+
/** description of the command option */
|
|
5
|
+
description: string;
|
|
6
|
+
/** defaults to undefined, provide shorter aliases as command options */
|
|
7
|
+
alias?: string | string[];
|
|
8
|
+
/** defaults to false, is the option required? */
|
|
9
|
+
required?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CommandOptions {
|
|
13
|
+
/** CLI command name, used in the help docs */
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
positional?: {
|
|
17
|
+
/** positional argument name (it will be displayed in the help docs) */
|
|
18
|
+
name: string;
|
|
19
|
+
/** positional argument description */
|
|
20
|
+
description: string;
|
|
21
|
+
/** postional argument type */
|
|
22
|
+
type?: 'string';
|
|
23
|
+
/** defaults to false, allows multiple values for this positional argument */
|
|
24
|
+
variadic?: boolean;
|
|
25
|
+
/** defaults to false, is the positional argument required? */
|
|
26
|
+
required?: boolean;
|
|
27
|
+
}[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Config {
|
|
31
|
+
command: CommandOptions;
|
|
32
|
+
options: Record<string, ArgumentOptions>;
|
|
33
|
+
version: string;
|
|
34
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Utility to convert kebab-case to camelCase */
|
|
2
|
+
export function kebabToCamel(str: string) {
|
|
3
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** Utility to convert camelCase to kebab-case */
|
|
7
|
+
export function camelToKebab(str: string) {
|
|
8
|
+
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** add whitespace padding to any input string */
|
|
12
|
+
export function padString(input: string, padding: number) {
|
|
13
|
+
return input.padEnd(padding);
|
|
14
|
+
}
|