@bemoje/cli 1.1.1 → 2.0.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/index.d.ts +27 -1
- package/index.mjs +1352 -0
- package/index.mjs.map +7 -0
- package/lib/Command.d.ts +97 -262
- package/lib/Help.d.ts +37 -83
- package/lib/helpers/findCommand.d.ts +6 -0
- package/lib/helpers/findOption.d.ts +5 -0
- package/lib/helpers/getCommandAncestors.d.ts +5 -0
- package/lib/helpers/getCommandAndAncestors.d.ts +5 -0
- package/lib/helpers/parseOptionFlags.d.ts +11 -0
- package/lib/internal/collectVariadicOptionValues.d.ts +7 -0
- package/lib/internal/mergeOptionDefaults.d.ts +3 -0
- package/lib/internal/normalizeArgv.d.ts +3 -0
- package/lib/internal/resolveArguments.d.ts +3 -0
- package/lib/internal/validateParsed.d.ts +3 -0
- package/lib/types.d.ts +383 -0
- package/package.json +35 -29
- package/LICENSE +0 -21
- package/README.md +0 -246
- package/index.js +0 -3
- package/lib/Command.js +0 -421
- package/lib/Help.js +0 -474
package/README.md
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
# @bemoje/cli
|
|
2
|
-
|
|
3
|
-
A type-safe CLI composer that can parse argv and generate help without execution coupling.
|
|
4
|
-
|
|
5
|
-
## Key Features
|
|
6
|
-
|
|
7
|
-
- **🎯 Composition-Focused**: Build command structures without execution logic.
|
|
8
|
-
- **🔒 Type-Safe**: Full TypeScript support with type inference for arguments and options
|
|
9
|
-
- **🎨 Flexible Help**: Fork of commander.js Help class with enhanced API and adapter support
|
|
10
|
-
- **✅ Validation**: Built-in CLI argument ordering validation and name conflict detection
|
|
11
|
-
|
|
12
|
-
## Quick Start
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
import { Command } from '@bemoje/cli'
|
|
16
|
-
|
|
17
|
-
const cmd = new Command('myapp')
|
|
18
|
-
.setVersion('1.0.0')
|
|
19
|
-
.setDescription('My awesome CLI application')
|
|
20
|
-
.addArgument('<input>', 'Input file path')
|
|
21
|
-
.addArgument('[output]', 'Output file path', { defaultValue: 'out.txt' })
|
|
22
|
-
.addOption('-v, --verbose', 'Enable verbose output')
|
|
23
|
-
.addOption('-f, --format <type>', 'Output format', { choices: ['json', 'xml', 'yaml'] })
|
|
24
|
-
|
|
25
|
-
console.log(cmd.parseArgv(['input.txt', '-v', '-f', 'json']))
|
|
26
|
-
// {
|
|
27
|
-
// command: [Getter],
|
|
28
|
-
// arguments: [ 'input.txt', 'out.txt' ],
|
|
29
|
-
// options: { verbose: true, format: 'json' }
|
|
30
|
-
// }
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Command Definition
|
|
34
|
-
|
|
35
|
-
### Basic Setup
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
const cmd = new Command('myapp')
|
|
39
|
-
.setVersion('1.0.0')
|
|
40
|
-
.setDescription('Application description')
|
|
41
|
-
.setSummary('Short summary for help')
|
|
42
|
-
.setAliases(['app', 'my-app'])
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Arguments (Positional)
|
|
46
|
-
|
|
47
|
-
Arguments follow strict ordering rules: required → optional → variadic
|
|
48
|
-
|
|
49
|
-
```ts
|
|
50
|
-
// Required argument
|
|
51
|
-
cmd.addArgument('<input>', 'Input file path')
|
|
52
|
-
|
|
53
|
-
// Optional argument with default
|
|
54
|
-
cmd.addArgument('[output]', 'Output file path', { defaultValue: 'dist/output.txt' })
|
|
55
|
-
|
|
56
|
-
// Required variadic (multiple values)
|
|
57
|
-
cmd.addArgument('<files...>', 'Multiple input files')
|
|
58
|
-
|
|
59
|
-
// Optional variadic with defaults
|
|
60
|
-
cmd.addArgument('[patterns...]', 'Glob patterns', { defaultValue: ['**/*.js'] })
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Options (Named Parameters)
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
// Boolean flag
|
|
67
|
-
cmd.addOption('-v, --verbose', 'Enable verbose output')
|
|
68
|
-
|
|
69
|
-
// Required string option
|
|
70
|
-
cmd.addOption('-f, --format <type>', 'Output format')
|
|
71
|
-
|
|
72
|
-
// Optional string option with default
|
|
73
|
-
cmd.addOption('-o, --output [path]', 'Output directory', { defaultValue: 'dist' })
|
|
74
|
-
|
|
75
|
-
// Required variadic option
|
|
76
|
-
cmd.addOption('-i, --include <patterns...>', 'Include patterns')
|
|
77
|
-
|
|
78
|
-
// Optional variadic option with defaults
|
|
79
|
-
cmd.addOption('-e, --exclude [patterns...]', 'Exclude patterns', {
|
|
80
|
-
defaultValue: ['node_modules', '.git'],
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
// Option with choices and environment variable
|
|
84
|
-
cmd.addOption('-l, --log-level [level]', 'Log level', {
|
|
85
|
-
choices: ['error', 'warn', 'info', 'debug'],
|
|
86
|
-
defaultValue: 'info',
|
|
87
|
-
env: 'LOG_LEVEL',
|
|
88
|
-
})
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Global Options
|
|
92
|
-
|
|
93
|
-
Options defined on parent commands are available to subcommands:
|
|
94
|
-
|
|
95
|
-
```ts
|
|
96
|
-
const app = new Command('myapp').addOption('-c, --config <file>', 'Config file')
|
|
97
|
-
|
|
98
|
-
app.addSubcommand('build').addOption('-w, --watch', 'Watch mode')
|
|
99
|
-
|
|
100
|
-
// Both --config and --watch are available to 'build' subcommand
|
|
101
|
-
const result = app.parseArgv(['build', '--config', 'myconfig.json', '--watch'])
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Subcommands
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
const cmd = new Command('git')
|
|
108
|
-
|
|
109
|
-
// Create subcommand
|
|
110
|
-
const add = cmd
|
|
111
|
-
.addSubcommand('add')
|
|
112
|
-
.setDescription('Add files to staging area')
|
|
113
|
-
.addArgument('<files...>', 'Files to add')
|
|
114
|
-
.addOption('-A, --all', 'Add all files')
|
|
115
|
-
|
|
116
|
-
const commit = cmd
|
|
117
|
-
.addSubcommand('commit')
|
|
118
|
-
.setDescription('Create a commit')
|
|
119
|
-
.addArgument('[message]', 'Commit message')
|
|
120
|
-
.addOption('-m, --message <msg>', 'Commit message')
|
|
121
|
-
.addOption('-a, --all', 'Commit all changes')
|
|
122
|
-
|
|
123
|
-
// Parsing automatically routes to subcommands
|
|
124
|
-
const result = cmd.parseArgv(['add', 'file1.js', 'file2.js', '-A'])
|
|
125
|
-
// result.command === add subcommand instance
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Help System
|
|
129
|
-
|
|
130
|
-
### Rendering Help
|
|
131
|
-
|
|
132
|
-
```ts
|
|
133
|
-
import { Help } from '@bemoje/cli'
|
|
134
|
-
|
|
135
|
-
// Use help formatting (requires Help instance)
|
|
136
|
-
const help = new Help()
|
|
137
|
-
console.log(cmd.renderHelp(help))
|
|
138
|
-
|
|
139
|
-
// Customize help configuration
|
|
140
|
-
const customHelp = new Help()
|
|
141
|
-
customHelp.helpWidth = 100
|
|
142
|
-
customHelp.sortOptions = true
|
|
143
|
-
customHelp.showGlobalOptions = true
|
|
144
|
-
console.log(cmd.renderHelp(customHelp))
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Help Configuration
|
|
148
|
-
|
|
149
|
-
Configure help behavior per command:
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
cmd.setHelpConfiguration({
|
|
153
|
-
sortOptions: true,
|
|
154
|
-
sortSubcommands: true,
|
|
155
|
-
showGlobalOptions: false,
|
|
156
|
-
helpWidth: 80,
|
|
157
|
-
})
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Custom Help Styling
|
|
161
|
-
|
|
162
|
-
It may be more convenient to extend the Help class for more extensive customization.
|
|
163
|
-
|
|
164
|
-
```ts
|
|
165
|
-
import { Help } from '@bemoje/cli'
|
|
166
|
-
|
|
167
|
-
class ColoredHelp extends Help {
|
|
168
|
-
styleTitle(str: string): string {
|
|
169
|
-
return `\x1b[1m${str}\x1b[0m` // Bold
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
styleOptionText(str: string): string {
|
|
173
|
-
return `\x1b[36m${str}\x1b[0m` // Cyan
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
styleArgumentText(str: string): string {
|
|
177
|
-
return `\x1b[33m${str}\x1b[0m` // Yellow
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
console.log(cmd.renderHelp(new ColoredHelp()))
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## Validation
|
|
185
|
-
|
|
186
|
-
Commands automatically validate:
|
|
187
|
-
|
|
188
|
-
- Argument ordering (required before optional before variadic)
|
|
189
|
-
|
|
190
|
-
```ts
|
|
191
|
-
cmd.addArgument('[optional]', 'Optional arg').addArgument('<required>', 'Required arg')
|
|
192
|
-
//=> ❌ Error!
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
- Unique option names and short flags, including globals across parent/child commands
|
|
196
|
-
|
|
197
|
-
```ts
|
|
198
|
-
cmd.addOption('-v, --verbose', 'Verbose output').addOption('-v, --video', 'Video mode')
|
|
199
|
-
//=> ❌ Error!
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
- Single variadic argument per command
|
|
203
|
-
|
|
204
|
-
```ts
|
|
205
|
-
cmd.addArgument('<files...>', 'First variadic').addArgument('<more...>', 'Second variadic')
|
|
206
|
-
//=> ❌ Error!
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## Command Class
|
|
210
|
-
|
|
211
|
-
**Constructor**:
|
|
212
|
-
|
|
213
|
-
```ts
|
|
214
|
-
- new (name: string, parent?: Command): Command
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
**Structure Methods**:
|
|
218
|
-
|
|
219
|
-
```ts
|
|
220
|
-
- addArgument(usage, description, options?): this
|
|
221
|
-
- addOption(usage, description, options?): this
|
|
222
|
-
- addSubcommand(name: string): Command
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**Configuration Methods**:
|
|
226
|
-
|
|
227
|
-
```ts
|
|
228
|
-
- setVersion(version?: string): this
|
|
229
|
-
- setName(name: string): this
|
|
230
|
-
- setDescription(...lines: string[]): this
|
|
231
|
-
- setSummary(summary?: string): this
|
|
232
|
-
- setHidden(hidden?: boolean): this
|
|
233
|
-
- setGroup(group?: string): this
|
|
234
|
-
- setHelpConfiguration(config?: Partial<IHelp>): this
|
|
235
|
-
- extendHelpConfiguration(config: Partial<IHelp>): this
|
|
236
|
-
- setAliases(...aliases: (string | string[])[]): this
|
|
237
|
-
- addAliases(...aliases: (string | string[])[]): this
|
|
238
|
-
- setParent(parent: Command | null): this
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
**Parsing & Help**:
|
|
242
|
-
|
|
243
|
-
```ts
|
|
244
|
-
- parseArgv(argv?: string[], globalOptions?: OptionDescriptor[]): ParseResult
|
|
245
|
-
- renderHelp(help: IHelp): string
|
|
246
|
-
```
|
package/index.js
DELETED
package/lib/Command.js
DELETED
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
-
import { parseArgs } from "node:util";
|
|
4
|
-
import { Help } from "./Help.js";
|
|
5
|
-
class Command {
|
|
6
|
-
static {
|
|
7
|
-
__name(this, "Command");
|
|
8
|
-
}
|
|
9
|
-
/** Command name used for invocation */
|
|
10
|
-
name;
|
|
11
|
-
/** Optional version string */
|
|
12
|
-
version;
|
|
13
|
-
/** Alternative names for this command */
|
|
14
|
-
aliases;
|
|
15
|
-
/** Brief single-line description */
|
|
16
|
-
summary;
|
|
17
|
-
/** Full command description */
|
|
18
|
-
description;
|
|
19
|
-
/** Whether command should be hidden from help */
|
|
20
|
-
hidden;
|
|
21
|
-
/** Group name for organizing commands in help */
|
|
22
|
-
group;
|
|
23
|
-
/** Parent command if this is a subcommand */
|
|
24
|
-
parent;
|
|
25
|
-
/** Child subcommands */
|
|
26
|
-
commands;
|
|
27
|
-
/** Positional arguments */
|
|
28
|
-
arguments;
|
|
29
|
-
/** Named options/flags */
|
|
30
|
-
options;
|
|
31
|
-
/** Help system configuration */
|
|
32
|
-
helpConfiguration;
|
|
33
|
-
constructor(name = "", parent = null) {
|
|
34
|
-
this.name = name;
|
|
35
|
-
this.parent = parent;
|
|
36
|
-
this.aliases = [];
|
|
37
|
-
this.description = "";
|
|
38
|
-
this.commands = [];
|
|
39
|
-
this.arguments = [];
|
|
40
|
-
this.options = [];
|
|
41
|
-
this.helpConfiguration = { showGlobalOptions: true, sortOptions: true, sortSubcommands: true };
|
|
42
|
-
Object.defineProperty(this, "parent", { enumerable: false });
|
|
43
|
-
}
|
|
44
|
-
/** Sets the command name */
|
|
45
|
-
setName(name) {
|
|
46
|
-
this.name = name;
|
|
47
|
-
}
|
|
48
|
-
/** Sets command aliases, flattening nested arrays */
|
|
49
|
-
setAliases(...aliases) {
|
|
50
|
-
this.aliases = aliases.flat();
|
|
51
|
-
return this;
|
|
52
|
-
}
|
|
53
|
-
/** Adds aliases to existing ones */
|
|
54
|
-
addAliases(...aliases) {
|
|
55
|
-
this.aliases.push(...aliases.flat());
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
/** Sets the command version */
|
|
59
|
-
setVersion(version) {
|
|
60
|
-
this.version = version;
|
|
61
|
-
return this;
|
|
62
|
-
}
|
|
63
|
-
/** Sets the command summary */
|
|
64
|
-
setSummary(summary) {
|
|
65
|
-
this.summary = summary;
|
|
66
|
-
return this;
|
|
67
|
-
}
|
|
68
|
-
/** Sets command description, joining variadic lines */
|
|
69
|
-
setDescription(...lines) {
|
|
70
|
-
this.description = lines.join("\n");
|
|
71
|
-
return this;
|
|
72
|
-
}
|
|
73
|
-
/** Sets whether command is hidden from help */
|
|
74
|
-
setHidden(hidden = true) {
|
|
75
|
-
this.hidden = hidden;
|
|
76
|
-
return this;
|
|
77
|
-
}
|
|
78
|
-
/** Sets the command group for help organization */
|
|
79
|
-
setGroup(group) {
|
|
80
|
-
this.group = group;
|
|
81
|
-
return this;
|
|
82
|
-
}
|
|
83
|
-
/** Sets the parent command */
|
|
84
|
-
setParent(parent) {
|
|
85
|
-
this.parent = parent;
|
|
86
|
-
return this;
|
|
87
|
-
}
|
|
88
|
-
/** Extends existing help configuration with new settings */
|
|
89
|
-
extendHelpConfiguration(config) {
|
|
90
|
-
this.helpConfiguration = { ...this.helpConfiguration, ...config };
|
|
91
|
-
return this;
|
|
92
|
-
}
|
|
93
|
-
/** Sets help configuration, using defaults if not provided */
|
|
94
|
-
setHelpConfiguration(config) {
|
|
95
|
-
this.helpConfiguration = config ? { ...config } : { showGlobalOptions: true, sortOptions: true, sortSubcommands: true };
|
|
96
|
-
return this;
|
|
97
|
-
}
|
|
98
|
-
/** Creates and adds a subcommand */
|
|
99
|
-
addSubcommand(name) {
|
|
100
|
-
const sub = this.createCommand(name, this);
|
|
101
|
-
this.commands.push(sub);
|
|
102
|
-
return sub;
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Adds positional argument with type inference and CLI ordering validation.
|
|
106
|
-
*/
|
|
107
|
-
addArgument(usage, description, options = {}) {
|
|
108
|
-
const match = usage.match(/^<(.*?)>$|^\[(.*?)\]$/);
|
|
109
|
-
if (!match) throw new Error(`Invalid argument format: ${usage}`);
|
|
110
|
-
const nameMatch = match[1] || match[2];
|
|
111
|
-
const name = nameMatch.replace(/\.\.\.$/, "");
|
|
112
|
-
this.assertArgumentNameNotInUse(name);
|
|
113
|
-
const props = { name, description };
|
|
114
|
-
if (usage.startsWith("<")) {
|
|
115
|
-
if (nameMatch.endsWith("...")) {
|
|
116
|
-
this.assertNoMultipleVariadicArguments();
|
|
117
|
-
this.arguments.push({
|
|
118
|
-
...options,
|
|
119
|
-
...props,
|
|
120
|
-
required: true,
|
|
121
|
-
variadic: true
|
|
122
|
-
});
|
|
123
|
-
} else {
|
|
124
|
-
this.assertNoOptionalOrVariadicArguments();
|
|
125
|
-
this.arguments.push({
|
|
126
|
-
...options,
|
|
127
|
-
...props,
|
|
128
|
-
required: true,
|
|
129
|
-
variadic: false
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
} else if (usage.startsWith("[")) {
|
|
133
|
-
if (nameMatch.endsWith("...")) {
|
|
134
|
-
this.assertNoMultipleVariadicArguments();
|
|
135
|
-
this.arguments.push({
|
|
136
|
-
...options,
|
|
137
|
-
...props,
|
|
138
|
-
required: false,
|
|
139
|
-
variadic: true,
|
|
140
|
-
defaultValue: options.defaultValue ?? []
|
|
141
|
-
});
|
|
142
|
-
} else {
|
|
143
|
-
this.assertNoVariadicArgument();
|
|
144
|
-
this.arguments.push({
|
|
145
|
-
...options,
|
|
146
|
-
...props,
|
|
147
|
-
required: false,
|
|
148
|
-
variadic: false
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return this;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Adds command-line option with type inference. Parses format: `-s, --long [<value>|[value]|<value...>|[value...]]`
|
|
156
|
-
*/
|
|
157
|
-
addOption(flags, description, opts = {}) {
|
|
158
|
-
const match = flags.match(/^-(.+?), --([a-zA-Z][\w-]*)(?:\s*(<(.+?)>|\[(.+?)\]))?$/);
|
|
159
|
-
if (!match) throw new Error(`Invalid option format: ${flags}`);
|
|
160
|
-
const short = match[1];
|
|
161
|
-
this.assertOptionShortNameIsValid(short);
|
|
162
|
-
this.assertOptionShortNameNotInUse(short);
|
|
163
|
-
const name = match[2];
|
|
164
|
-
const argName = (match[4] || match[5])?.replace(/\.\.\.$/, "");
|
|
165
|
-
this.assertOptionNameNotInUse(name);
|
|
166
|
-
const props = {
|
|
167
|
-
flags,
|
|
168
|
-
short,
|
|
169
|
-
long: name,
|
|
170
|
-
name,
|
|
171
|
-
description
|
|
172
|
-
};
|
|
173
|
-
if (!argName) {
|
|
174
|
-
this._addOption({
|
|
175
|
-
type: "boolean",
|
|
176
|
-
...opts,
|
|
177
|
-
...props,
|
|
178
|
-
negate: false,
|
|
179
|
-
optional: true,
|
|
180
|
-
variadic: false,
|
|
181
|
-
get multiple() {
|
|
182
|
-
return this.variadic;
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
} else if (flags.endsWith(">")) {
|
|
186
|
-
if (flags.endsWith("...>")) {
|
|
187
|
-
this._addOption({
|
|
188
|
-
type: "string",
|
|
189
|
-
...opts,
|
|
190
|
-
...props,
|
|
191
|
-
argName,
|
|
192
|
-
required: true,
|
|
193
|
-
optional: false,
|
|
194
|
-
variadic: true,
|
|
195
|
-
get multiple() {
|
|
196
|
-
return this.variadic;
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
} else {
|
|
200
|
-
this._addOption({
|
|
201
|
-
type: "string",
|
|
202
|
-
...opts,
|
|
203
|
-
...props,
|
|
204
|
-
argName,
|
|
205
|
-
required: true,
|
|
206
|
-
optional: false,
|
|
207
|
-
variadic: false,
|
|
208
|
-
get multiple() {
|
|
209
|
-
return this.variadic;
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
} else if (flags.endsWith("]")) {
|
|
214
|
-
if (flags.endsWith("...]")) {
|
|
215
|
-
this._addOption({
|
|
216
|
-
type: "string",
|
|
217
|
-
...opts,
|
|
218
|
-
...props,
|
|
219
|
-
argName,
|
|
220
|
-
required: false,
|
|
221
|
-
optional: true,
|
|
222
|
-
variadic: true,
|
|
223
|
-
get multiple() {
|
|
224
|
-
return this.variadic;
|
|
225
|
-
},
|
|
226
|
-
defaultValue: opts.defaultValue ?? []
|
|
227
|
-
});
|
|
228
|
-
} else {
|
|
229
|
-
this._addOption({
|
|
230
|
-
type: "string",
|
|
231
|
-
...opts,
|
|
232
|
-
...props,
|
|
233
|
-
argName,
|
|
234
|
-
required: false,
|
|
235
|
-
optional: true,
|
|
236
|
-
variadic: false,
|
|
237
|
-
get multiple() {
|
|
238
|
-
return this.variadic;
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return this;
|
|
244
|
-
}
|
|
245
|
-
_addOption(opt) {
|
|
246
|
-
this.options.push(opt);
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Parses command-line arguments with subcommand support and type-safe validation.
|
|
250
|
-
*
|
|
251
|
-
* @example
|
|
252
|
-
* ```typescript
|
|
253
|
-
* const result = cmd.parse(['input.txt', '-v', '--format', 'json'])
|
|
254
|
-
* // { arguments: ['input.txt'], options: { verbose: true, format: 'json' } }
|
|
255
|
-
* ```
|
|
256
|
-
*/
|
|
257
|
-
parseArgv(argv = process.argv.slice(2), globalOptions = []) {
|
|
258
|
-
const maybeSubArg = parseArgs({
|
|
259
|
-
args: argv,
|
|
260
|
-
allowPositionals: true,
|
|
261
|
-
tokens: false,
|
|
262
|
-
strict: false,
|
|
263
|
-
allowNegative: true
|
|
264
|
-
}).positionals[0];
|
|
265
|
-
const sub = this.findCommand(maybeSubArg);
|
|
266
|
-
if (sub) {
|
|
267
|
-
return sub.parseArgv(
|
|
268
|
-
argv?.filter((a) => a !== maybeSubArg),
|
|
269
|
-
[...globalOptions, ...this.options]
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
const parsed = parseArgs({
|
|
273
|
-
args: argv,
|
|
274
|
-
options: Object.fromEntries(
|
|
275
|
-
[...globalOptions, ...this.options].map((o) => {
|
|
276
|
-
return [o.name, o];
|
|
277
|
-
})
|
|
278
|
-
),
|
|
279
|
-
allowPositionals: true,
|
|
280
|
-
tokens: true,
|
|
281
|
-
strict: true,
|
|
282
|
-
allowNegative: true
|
|
283
|
-
});
|
|
284
|
-
for (let i = 0; i < parsed.tokens.length; i++) {
|
|
285
|
-
const token = parsed.tokens[i];
|
|
286
|
-
if (token.kind === "option") {
|
|
287
|
-
const optionDescriptor = this.options.find((o) => o.name === token.name);
|
|
288
|
-
if (optionDescriptor && optionDescriptor.variadic && optionDescriptor.type === "string") {
|
|
289
|
-
const values = [token.value];
|
|
290
|
-
let j = i + 1;
|
|
291
|
-
while (j < parsed.tokens.length && parsed.tokens[j].kind === "positional") {
|
|
292
|
-
const positionalToken = parsed.tokens[j];
|
|
293
|
-
if (positionalToken.kind === "positional") {
|
|
294
|
-
values.push(positionalToken.value);
|
|
295
|
-
const posIndex = parsed.positionals.indexOf(positionalToken.value);
|
|
296
|
-
if (posIndex !== -1) {
|
|
297
|
-
parsed.positionals.splice(posIndex, 1);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
j++;
|
|
301
|
-
}
|
|
302
|
-
Reflect.set(
|
|
303
|
-
parsed.values,
|
|
304
|
-
token.name,
|
|
305
|
-
values.filter((v) => v !== void 0)
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
const parsedArguments = this.arguments.map((arg, index) => {
|
|
311
|
-
if (arg.variadic) {
|
|
312
|
-
const remainingArgs = parsed.positionals.slice(index);
|
|
313
|
-
return remainingArgs.length > 0 ? remainingArgs : arg.defaultValue ?? [];
|
|
314
|
-
} else {
|
|
315
|
-
return parsed.positionals[index] ?? arg.defaultValue;
|
|
316
|
-
}
|
|
317
|
-
}).filter((arg) => arg !== void 0);
|
|
318
|
-
for (const option of this.options) {
|
|
319
|
-
if (!(option.name in parsed.values) && "defaultValue" in option) {
|
|
320
|
-
Reflect.set(parsed.values, option.name, option.defaultValue);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
const self = this;
|
|
324
|
-
return {
|
|
325
|
-
get command() {
|
|
326
|
-
return self;
|
|
327
|
-
},
|
|
328
|
-
arguments: parsedArguments,
|
|
329
|
-
options: { ...parsed.values }
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
/** Renders formatted help text using provided help definition */
|
|
333
|
-
renderHelp(help = new Help()) {
|
|
334
|
-
const helper = Object.assign(help, this.helpConfiguration);
|
|
335
|
-
return helper.formatHelp(this, helper);
|
|
336
|
-
}
|
|
337
|
-
/** Validates CLI argument ordering */
|
|
338
|
-
assertNoOptionalOrVariadicArguments() {
|
|
339
|
-
if (this.arguments.some((arg) => !arg.required)) {
|
|
340
|
-
throw new Error("Cannot add required argument after optional or variadic arguments");
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
/** Validates optional args don't follow variadic args */
|
|
344
|
-
assertNoVariadicArgument() {
|
|
345
|
-
if (this.arguments.some((arg) => arg.variadic)) {
|
|
346
|
-
throw new Error("Cannot add optional argument after variadic argument");
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
/** Ensures only one variadic argument per command */
|
|
350
|
-
assertNoMultipleVariadicArguments() {
|
|
351
|
-
if (this.arguments.some((arg) => arg.variadic)) {
|
|
352
|
-
throw new Error("Cannot add more than one variadic argument");
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
/** Ensures unique argument names across arguments and options */
|
|
356
|
-
assertArgumentNameNotInUse(name) {
|
|
357
|
-
if (this.arguments.some((arg) => arg.name === name)) {
|
|
358
|
-
throw new Error(`Argument name already in use: ${name}`);
|
|
359
|
-
}
|
|
360
|
-
if (this.options.some((opt) => opt.name === name)) {
|
|
361
|
-
throw new Error(`Argument name already in use: ${name}`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
/** Validates option short names are single alphanumeric characters */
|
|
365
|
-
assertOptionShortNameIsValid(short) {
|
|
366
|
-
const isSingleAlphaNumericChar = /^[a-zA-Z0-9]$/.test(short);
|
|
367
|
-
if (!isSingleAlphaNumericChar) {
|
|
368
|
-
throw new Error(`Expected short name to be a single alpha-numeric character. Got: ${short}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
/** Validates option short names are unique across command hierarchy */
|
|
372
|
-
assertOptionShortNameNotInUse(short) {
|
|
373
|
-
for (const opt of this.options) {
|
|
374
|
-
if (opt.short === short) {
|
|
375
|
-
throw new Error(`Option short name already in use: -${short}`);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
this.parent?.assertOptionShortNameNotInUse(short);
|
|
379
|
-
}
|
|
380
|
-
/** Validates option names are unique across command hierarchy */
|
|
381
|
-
assertOptionNameNotInUse(name) {
|
|
382
|
-
if (this.options.some((opt) => opt.name === name)) {
|
|
383
|
-
throw new Error(`Option name already in use: --${name}`);
|
|
384
|
-
}
|
|
385
|
-
this.parent?.assertOptionNameNotInUse(name);
|
|
386
|
-
}
|
|
387
|
-
/** Returns command and all ancestor commands in hierarchy */
|
|
388
|
-
getCommandAndAncestors() {
|
|
389
|
-
const result = [];
|
|
390
|
-
let command = this;
|
|
391
|
-
for (; command; command = command.parent) {
|
|
392
|
-
result.push(command);
|
|
393
|
-
}
|
|
394
|
-
return result;
|
|
395
|
-
}
|
|
396
|
-
/** Returns all ancestor commands excluding this command */
|
|
397
|
-
getAncestors() {
|
|
398
|
-
return this.getCommandAndAncestors().slice(1);
|
|
399
|
-
}
|
|
400
|
-
/** Returns all options from this command and ancestors */
|
|
401
|
-
getOptionsInclAncestors() {
|
|
402
|
-
return this.getCommandAndAncestors().flatMap((cmd) => cmd.options);
|
|
403
|
-
}
|
|
404
|
-
/** Finds subcommand by name or alias */
|
|
405
|
-
findCommand(name) {
|
|
406
|
-
if (!name) return void 0;
|
|
407
|
-
return this.commands.find((cmd) => cmd.name === name || cmd.aliases.includes(name));
|
|
408
|
-
}
|
|
409
|
-
/** Finds option by short or long name */
|
|
410
|
-
findOption(arg) {
|
|
411
|
-
return this.options.find((option) => option.short === arg || option.name === arg);
|
|
412
|
-
}
|
|
413
|
-
/** Returns a new Command instance. Override this method in subclasses. */
|
|
414
|
-
createCommand(name = "", parent = null) {
|
|
415
|
-
return new Command(name, parent);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
export {
|
|
419
|
-
Command
|
|
420
|
-
};
|
|
421
|
-
//# sourceMappingURL=Command.js.map
|