@xmorse/cac 6.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/LICENSE +21 -0
- package/README.md +536 -0
- package/deno/CAC.ts +306 -0
- package/deno/Command.ts +257 -0
- package/deno/Option.ts +46 -0
- package/deno/deno.ts +5 -0
- package/deno/index.ts +9 -0
- package/deno/utils.ts +129 -0
- package/dist/index.d.ts +189 -0
- package/dist/index.js +645 -0
- package/dist/index.mjs +638 -0
- package/index-compat.js +11 -0
- package/mod.js +2 -0
- package/mod.ts +2 -0
- package/package.json +103 -0
package/deno/CAC.ts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { EventEmitter } from "https://deno.land/std@0.114.0/node/events.ts";
|
|
2
|
+
import mri from "https://cdn.skypack.dev/mri";
|
|
3
|
+
import Command, { GlobalCommand, CommandConfig, HelpCallback, CommandExample } from "./Command.ts";
|
|
4
|
+
import { OptionConfig } from "./Option.ts";
|
|
5
|
+
import { getMriOptions, setDotProp, setByType, getFileName, camelcaseOptionName } from "./utils.ts";
|
|
6
|
+
import { processArgs } from "./deno.ts";
|
|
7
|
+
interface ParsedArgv {
|
|
8
|
+
args: ReadonlyArray<string>;
|
|
9
|
+
options: {
|
|
10
|
+
[k: string]: any;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
class CAC extends EventEmitter {
|
|
14
|
+
/** The program name to display in help and version message */
|
|
15
|
+
name: string;
|
|
16
|
+
commands: Command[];
|
|
17
|
+
globalCommand: GlobalCommand;
|
|
18
|
+
matchedCommand?: Command;
|
|
19
|
+
matchedCommandName?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Raw CLI arguments
|
|
22
|
+
*/
|
|
23
|
+
rawArgs: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Parsed CLI arguments
|
|
26
|
+
*/
|
|
27
|
+
args: ParsedArgv['args'];
|
|
28
|
+
/**
|
|
29
|
+
* Parsed CLI options, camelCased
|
|
30
|
+
*/
|
|
31
|
+
options: ParsedArgv['options'];
|
|
32
|
+
showHelpOnExit?: boolean;
|
|
33
|
+
showVersionOnExit?: boolean;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param name The program name to display in help and version message
|
|
37
|
+
*/
|
|
38
|
+
constructor(name = '') {
|
|
39
|
+
super();
|
|
40
|
+
this.name = name;
|
|
41
|
+
this.commands = [];
|
|
42
|
+
this.rawArgs = [];
|
|
43
|
+
this.args = [];
|
|
44
|
+
this.options = {};
|
|
45
|
+
this.globalCommand = new GlobalCommand(this);
|
|
46
|
+
this.globalCommand.usage('<command> [options]');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Add a global usage text.
|
|
51
|
+
*
|
|
52
|
+
* This is not used by sub-commands.
|
|
53
|
+
*/
|
|
54
|
+
usage(text: string) {
|
|
55
|
+
this.globalCommand.usage(text);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Add a sub-command
|
|
61
|
+
*/
|
|
62
|
+
command(rawName: string, description?: string, config?: CommandConfig) {
|
|
63
|
+
const command = new Command(rawName, description || '', config, this);
|
|
64
|
+
command.globalCommand = this.globalCommand;
|
|
65
|
+
this.commands.push(command);
|
|
66
|
+
return command;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Add a global CLI option.
|
|
71
|
+
*
|
|
72
|
+
* Which is also applied to sub-commands.
|
|
73
|
+
*/
|
|
74
|
+
option(rawName: string, description: string, config?: OptionConfig) {
|
|
75
|
+
this.globalCommand.option(rawName, description, config);
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Show help message when `-h, --help` flags appear.
|
|
81
|
+
*
|
|
82
|
+
*/
|
|
83
|
+
help(callback?: HelpCallback) {
|
|
84
|
+
this.globalCommand.option('-h, --help', 'Display this message');
|
|
85
|
+
this.globalCommand.helpCallback = callback;
|
|
86
|
+
this.showHelpOnExit = true;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Show version number when `-v, --version` flags appear.
|
|
92
|
+
*
|
|
93
|
+
*/
|
|
94
|
+
version(version: string, customFlags = '-v, --version') {
|
|
95
|
+
this.globalCommand.version(version, customFlags);
|
|
96
|
+
this.showVersionOnExit = true;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Add a global example.
|
|
102
|
+
*
|
|
103
|
+
* This example added here will not be used by sub-commands.
|
|
104
|
+
*/
|
|
105
|
+
example(example: CommandExample) {
|
|
106
|
+
this.globalCommand.example(example);
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Output the corresponding help message
|
|
112
|
+
* When a sub-command is matched, output the help message for the command
|
|
113
|
+
* Otherwise output the global one.
|
|
114
|
+
*
|
|
115
|
+
*/
|
|
116
|
+
outputHelp() {
|
|
117
|
+
if (this.matchedCommand) {
|
|
118
|
+
this.matchedCommand.outputHelp();
|
|
119
|
+
} else {
|
|
120
|
+
this.globalCommand.outputHelp();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Output the version number.
|
|
126
|
+
*
|
|
127
|
+
*/
|
|
128
|
+
outputVersion() {
|
|
129
|
+
this.globalCommand.outputVersion();
|
|
130
|
+
}
|
|
131
|
+
private setParsedInfo({
|
|
132
|
+
args,
|
|
133
|
+
options
|
|
134
|
+
}: ParsedArgv, matchedCommand?: Command, matchedCommandName?: string) {
|
|
135
|
+
this.args = args;
|
|
136
|
+
this.options = options;
|
|
137
|
+
if (matchedCommand) {
|
|
138
|
+
this.matchedCommand = matchedCommand;
|
|
139
|
+
}
|
|
140
|
+
if (matchedCommandName) {
|
|
141
|
+
this.matchedCommandName = matchedCommandName;
|
|
142
|
+
}
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
unsetMatchedCommand() {
|
|
146
|
+
this.matchedCommand = undefined;
|
|
147
|
+
this.matchedCommandName = undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Parse argv
|
|
152
|
+
*/
|
|
153
|
+
parse(argv = processArgs, {
|
|
154
|
+
/** Whether to run the action for matched command */
|
|
155
|
+
run = true
|
|
156
|
+
} = {}): ParsedArgv {
|
|
157
|
+
this.rawArgs = argv;
|
|
158
|
+
if (!this.name) {
|
|
159
|
+
this.name = argv[1] ? getFileName(argv[1]) : 'cli';
|
|
160
|
+
}
|
|
161
|
+
let shouldParse = true;
|
|
162
|
+
|
|
163
|
+
// Sort by name length (longest first) so "mcp login" matches before "mcp"
|
|
164
|
+
const sortedCommands = [...this.commands].sort((a, b) => {
|
|
165
|
+
const aLength = a.name.split(' ').filter(Boolean).length;
|
|
166
|
+
const bLength = b.name.split(' ').filter(Boolean).length;
|
|
167
|
+
return bLength - aLength;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Search sub-commands
|
|
171
|
+
for (const command of sortedCommands) {
|
|
172
|
+
const parsed = this.mri(argv.slice(2), command);
|
|
173
|
+
const result = command.isMatched((parsed.args as string[]));
|
|
174
|
+
if (result.matched) {
|
|
175
|
+
shouldParse = false;
|
|
176
|
+
const matchedCommandName = parsed.args.slice(0, result.consumedArgs).join(' ');
|
|
177
|
+
const parsedInfo = {
|
|
178
|
+
...parsed,
|
|
179
|
+
args: parsed.args.slice(result.consumedArgs)
|
|
180
|
+
};
|
|
181
|
+
this.setParsedInfo(parsedInfo, command, matchedCommandName);
|
|
182
|
+
this.emit(`command:${matchedCommandName}`, command);
|
|
183
|
+
break; // Stop after first match (greedy matching)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (shouldParse) {
|
|
187
|
+
// Search the default command
|
|
188
|
+
for (const command of this.commands) {
|
|
189
|
+
if (command.name === '') {
|
|
190
|
+
shouldParse = false;
|
|
191
|
+
const parsed = this.mri(argv.slice(2), command);
|
|
192
|
+
this.setParsedInfo(parsed, command);
|
|
193
|
+
this.emit(`command:!`, command);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (shouldParse) {
|
|
198
|
+
const parsed = this.mri(argv.slice(2));
|
|
199
|
+
this.setParsedInfo(parsed);
|
|
200
|
+
}
|
|
201
|
+
if (this.options.help && this.showHelpOnExit) {
|
|
202
|
+
this.outputHelp();
|
|
203
|
+
run = false;
|
|
204
|
+
this.unsetMatchedCommand();
|
|
205
|
+
}
|
|
206
|
+
if (this.options.version && this.showVersionOnExit && this.matchedCommandName == null) {
|
|
207
|
+
this.outputVersion();
|
|
208
|
+
run = false;
|
|
209
|
+
this.unsetMatchedCommand();
|
|
210
|
+
}
|
|
211
|
+
const parsedArgv = {
|
|
212
|
+
args: this.args,
|
|
213
|
+
options: this.options
|
|
214
|
+
};
|
|
215
|
+
if (run) {
|
|
216
|
+
this.runMatchedCommand();
|
|
217
|
+
}
|
|
218
|
+
if (!this.matchedCommand && this.args[0]) {
|
|
219
|
+
this.emit('command:*');
|
|
220
|
+
}
|
|
221
|
+
return parsedArgv;
|
|
222
|
+
}
|
|
223
|
+
private mri(argv: string[], /** Matched command */command?: Command): ParsedArgv {
|
|
224
|
+
// All added options
|
|
225
|
+
const cliOptions = [...this.globalCommand.options, ...(command ? command.options : [])];
|
|
226
|
+
const mriOptions = getMriOptions(cliOptions);
|
|
227
|
+
|
|
228
|
+
// Extract everything after `--` since mri doesn't support it
|
|
229
|
+
let argsAfterDoubleDashes: string[] = [];
|
|
230
|
+
const doubleDashesIndex = argv.indexOf('--');
|
|
231
|
+
if (doubleDashesIndex > -1) {
|
|
232
|
+
argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
|
|
233
|
+
argv = argv.slice(0, doubleDashesIndex);
|
|
234
|
+
}
|
|
235
|
+
let parsed = mri(argv, mriOptions);
|
|
236
|
+
parsed = Object.keys(parsed).reduce((res, name) => {
|
|
237
|
+
return {
|
|
238
|
+
...res,
|
|
239
|
+
[camelcaseOptionName(name)]: parsed[name]
|
|
240
|
+
};
|
|
241
|
+
}, {
|
|
242
|
+
_: []
|
|
243
|
+
});
|
|
244
|
+
const args = parsed._;
|
|
245
|
+
const options: {
|
|
246
|
+
[k: string]: any;
|
|
247
|
+
} = {
|
|
248
|
+
'--': argsAfterDoubleDashes
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Set option default value
|
|
252
|
+
const ignoreDefault = command && command.config.ignoreOptionDefaultValue ? command.config.ignoreOptionDefaultValue : this.globalCommand.config.ignoreOptionDefaultValue;
|
|
253
|
+
let transforms = Object.create(null);
|
|
254
|
+
for (const cliOption of cliOptions) {
|
|
255
|
+
if (!ignoreDefault && cliOption.config.default !== undefined) {
|
|
256
|
+
for (const name of cliOption.names) {
|
|
257
|
+
options[name] = cliOption.config.default;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// If options type is defined
|
|
262
|
+
if (Array.isArray(cliOption.config.type)) {
|
|
263
|
+
if (transforms[cliOption.name] === undefined) {
|
|
264
|
+
transforms[cliOption.name] = Object.create(null);
|
|
265
|
+
transforms[cliOption.name]['shouldTransform'] = true;
|
|
266
|
+
transforms[cliOption.name]['transformFunction'] = cliOption.config.type[0];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Set option values (support dot-nested property name)
|
|
272
|
+
for (const key of Object.keys(parsed)) {
|
|
273
|
+
if (key !== '_') {
|
|
274
|
+
const keys = key.split('.');
|
|
275
|
+
setDotProp(options, keys, parsed[key]);
|
|
276
|
+
setByType(options, transforms);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
args,
|
|
281
|
+
options
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
runMatchedCommand() {
|
|
285
|
+
const {
|
|
286
|
+
args,
|
|
287
|
+
options,
|
|
288
|
+
matchedCommand: command
|
|
289
|
+
} = this;
|
|
290
|
+
if (!command || !command.commandAction) return;
|
|
291
|
+
command.checkUnknownOptions();
|
|
292
|
+
command.checkOptionValue();
|
|
293
|
+
command.checkRequiredArgs();
|
|
294
|
+
const actionArgs: any[] = [];
|
|
295
|
+
command.args.forEach((arg, index) => {
|
|
296
|
+
if (arg.variadic) {
|
|
297
|
+
actionArgs.push(args.slice(index));
|
|
298
|
+
} else {
|
|
299
|
+
actionArgs.push(args[index]);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
actionArgs.push(options);
|
|
303
|
+
return command.commandAction.apply(this, actionArgs);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
export default CAC;
|
package/deno/Command.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import CAC from "./CAC.ts";
|
|
2
|
+
import Option, { OptionConfig } from "./Option.ts";
|
|
3
|
+
import { removeBrackets, findAllBrackets, findLongest, padRight, CACError } from "./utils.ts";
|
|
4
|
+
import { platformInfo } from "./deno.ts";
|
|
5
|
+
interface CommandArg {
|
|
6
|
+
required: boolean;
|
|
7
|
+
value: string;
|
|
8
|
+
variadic: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface HelpSection {
|
|
11
|
+
title?: string;
|
|
12
|
+
body: string;
|
|
13
|
+
}
|
|
14
|
+
interface CommandConfig {
|
|
15
|
+
allowUnknownOptions?: boolean;
|
|
16
|
+
ignoreOptionDefaultValue?: boolean;
|
|
17
|
+
}
|
|
18
|
+
type HelpCallback = (sections: HelpSection[]) => void | HelpSection[];
|
|
19
|
+
type CommandExample = ((bin: string) => string) | string;
|
|
20
|
+
class Command {
|
|
21
|
+
options: Option[];
|
|
22
|
+
aliasNames: string[];
|
|
23
|
+
/* Parsed command name */
|
|
24
|
+
name: string;
|
|
25
|
+
args: CommandArg[];
|
|
26
|
+
commandAction?: (...args: any[]) => any;
|
|
27
|
+
usageText?: string;
|
|
28
|
+
versionNumber?: string;
|
|
29
|
+
examples: CommandExample[];
|
|
30
|
+
helpCallback?: HelpCallback;
|
|
31
|
+
globalCommand?: GlobalCommand;
|
|
32
|
+
constructor(public rawName: string, public description: string, public config: CommandConfig = {}, public cli: CAC) {
|
|
33
|
+
this.options = [];
|
|
34
|
+
this.aliasNames = [];
|
|
35
|
+
this.name = removeBrackets(rawName);
|
|
36
|
+
this.args = findAllBrackets(rawName);
|
|
37
|
+
this.examples = [];
|
|
38
|
+
}
|
|
39
|
+
usage(text: string) {
|
|
40
|
+
this.usageText = text;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
allowUnknownOptions() {
|
|
44
|
+
this.config.allowUnknownOptions = true;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
ignoreOptionDefaultValue() {
|
|
48
|
+
this.config.ignoreOptionDefaultValue = true;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
version(version: string, customFlags = '-v, --version') {
|
|
52
|
+
this.versionNumber = version;
|
|
53
|
+
this.option(customFlags, 'Display version number');
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
example(example: CommandExample) {
|
|
57
|
+
this.examples.push(example);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Add a option for this command
|
|
63
|
+
* @param rawName Raw option name(s)
|
|
64
|
+
* @param description Option description
|
|
65
|
+
* @param config Option config
|
|
66
|
+
*/
|
|
67
|
+
option(rawName: string, description: string, config?: OptionConfig) {
|
|
68
|
+
const option = new Option(rawName, description, config);
|
|
69
|
+
this.options.push(option);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
alias(name: string) {
|
|
73
|
+
this.aliasNames.push(name);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
action(callback: (...args: any[]) => any) {
|
|
77
|
+
this.commandAction = callback;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
isMatched(args: string[]): {
|
|
81
|
+
matched: boolean;
|
|
82
|
+
consumedArgs: number;
|
|
83
|
+
} {
|
|
84
|
+
const nameParts = this.name.split(' ').filter(Boolean);
|
|
85
|
+
if (nameParts.length === 0) {
|
|
86
|
+
return {
|
|
87
|
+
matched: false,
|
|
88
|
+
consumedArgs: 0
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (args.length < nameParts.length) {
|
|
92
|
+
return {
|
|
93
|
+
matched: false,
|
|
94
|
+
consumedArgs: 0
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
for (let i = 0; i < nameParts.length; i++) {
|
|
98
|
+
if (nameParts[i] !== args[i]) {
|
|
99
|
+
if (i === 0 && this.aliasNames.includes(args[i])) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
matched: false,
|
|
104
|
+
consumedArgs: 0
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
matched: true,
|
|
110
|
+
consumedArgs: nameParts.length
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
get isDefaultCommand() {
|
|
114
|
+
return this.name === '' || this.aliasNames.includes('!');
|
|
115
|
+
}
|
|
116
|
+
get isGlobalCommand(): boolean {
|
|
117
|
+
return this instanceof GlobalCommand;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if an option is registered in this command
|
|
122
|
+
* @param name Option name
|
|
123
|
+
*/
|
|
124
|
+
hasOption(name: string) {
|
|
125
|
+
name = name.split('.')[0];
|
|
126
|
+
return this.options.find(option => {
|
|
127
|
+
return option.names.includes(name);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
outputHelp() {
|
|
131
|
+
const {
|
|
132
|
+
name,
|
|
133
|
+
commands
|
|
134
|
+
} = this.cli;
|
|
135
|
+
const {
|
|
136
|
+
versionNumber,
|
|
137
|
+
options: globalOptions,
|
|
138
|
+
helpCallback
|
|
139
|
+
} = this.cli.globalCommand;
|
|
140
|
+
let sections: HelpSection[] = [{
|
|
141
|
+
body: `${name}${versionNumber ? `/${versionNumber}` : ''}`
|
|
142
|
+
}];
|
|
143
|
+
sections.push({
|
|
144
|
+
title: 'Usage',
|
|
145
|
+
body: ` $ ${name} ${this.usageText || this.rawName}`
|
|
146
|
+
});
|
|
147
|
+
const showCommands = (this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0;
|
|
148
|
+
if (showCommands) {
|
|
149
|
+
const longestCommandName = findLongest(commands.map(command => command.rawName));
|
|
150
|
+
sections.push({
|
|
151
|
+
title: 'Commands',
|
|
152
|
+
body: commands.map(command => {
|
|
153
|
+
return ` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`;
|
|
154
|
+
}).join('\n')
|
|
155
|
+
});
|
|
156
|
+
sections.push({
|
|
157
|
+
title: `For more info, run any command with the \`--help\` flag`,
|
|
158
|
+
body: commands.map(command => ` $ ${name}${command.name === '' ? '' : ` ${command.name}`} --help`).join('\n')
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
let options = this.isGlobalCommand ? globalOptions : [...this.options, ...(globalOptions || [])];
|
|
162
|
+
if (!this.isGlobalCommand && !this.isDefaultCommand) {
|
|
163
|
+
options = options.filter(option => option.name !== 'version');
|
|
164
|
+
}
|
|
165
|
+
if (options.length > 0) {
|
|
166
|
+
const longestOptionName = findLongest(options.map(option => option.rawName));
|
|
167
|
+
sections.push({
|
|
168
|
+
title: 'Options',
|
|
169
|
+
body: options.map(option => {
|
|
170
|
+
return ` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${option.config.default === undefined ? '' : `(default: ${option.config.default})`}`;
|
|
171
|
+
}).join('\n')
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (this.examples.length > 0) {
|
|
175
|
+
sections.push({
|
|
176
|
+
title: 'Examples',
|
|
177
|
+
body: this.examples.map(example => {
|
|
178
|
+
if (typeof example === 'function') {
|
|
179
|
+
return example(name);
|
|
180
|
+
}
|
|
181
|
+
return example;
|
|
182
|
+
}).join('\n')
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (helpCallback) {
|
|
186
|
+
sections = helpCallback(sections) || sections;
|
|
187
|
+
}
|
|
188
|
+
console.log(sections.map(section => {
|
|
189
|
+
return section.title ? `${section.title}:\n${section.body}` : section.body;
|
|
190
|
+
}).join('\n\n'));
|
|
191
|
+
}
|
|
192
|
+
outputVersion() {
|
|
193
|
+
const {
|
|
194
|
+
name
|
|
195
|
+
} = this.cli;
|
|
196
|
+
const {
|
|
197
|
+
versionNumber
|
|
198
|
+
} = this.cli.globalCommand;
|
|
199
|
+
if (versionNumber) {
|
|
200
|
+
console.log(`${name}/${versionNumber} ${platformInfo}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
checkRequiredArgs() {
|
|
204
|
+
const minimalArgsCount = this.args.filter(arg => arg.required).length;
|
|
205
|
+
if (this.cli.args.length < minimalArgsCount) {
|
|
206
|
+
throw new CACError(`missing required args for command \`${this.rawName}\``);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check if the parsed options contain any unknown options
|
|
212
|
+
*
|
|
213
|
+
* Exit and output error when true
|
|
214
|
+
*/
|
|
215
|
+
checkUnknownOptions() {
|
|
216
|
+
const {
|
|
217
|
+
options,
|
|
218
|
+
globalCommand
|
|
219
|
+
} = this.cli;
|
|
220
|
+
if (!this.config.allowUnknownOptions) {
|
|
221
|
+
for (const name of Object.keys(options)) {
|
|
222
|
+
if (name !== '--' && !this.hasOption(name) && !globalCommand.hasOption(name)) {
|
|
223
|
+
throw new CACError(`Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if the required string-type options exist
|
|
231
|
+
*/
|
|
232
|
+
checkOptionValue() {
|
|
233
|
+
const {
|
|
234
|
+
options: parsedOptions,
|
|
235
|
+
globalCommand
|
|
236
|
+
} = this.cli;
|
|
237
|
+
const options = [...globalCommand.options, ...this.options];
|
|
238
|
+
for (const option of options) {
|
|
239
|
+
const value = parsedOptions[option.name.split('.')[0]];
|
|
240
|
+
// Check required option value
|
|
241
|
+
if (option.required) {
|
|
242
|
+
const hasNegated = options.some(o => o.negated && o.names.includes(option.name));
|
|
243
|
+
if (value === true || value === false && !hasNegated) {
|
|
244
|
+
throw new CACError(`option \`${option.rawName}\` value is missing`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
class GlobalCommand extends Command {
|
|
251
|
+
constructor(cli: CAC) {
|
|
252
|
+
super('@@global@@', '', {}, cli);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export type { HelpCallback, CommandExample, CommandConfig };
|
|
256
|
+
export { GlobalCommand };
|
|
257
|
+
export default Command;
|
package/deno/Option.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { removeBrackets, camelcaseOptionName } from "./utils.ts";
|
|
2
|
+
interface OptionConfig {
|
|
3
|
+
default?: any;
|
|
4
|
+
type?: any[];
|
|
5
|
+
}
|
|
6
|
+
export default class Option {
|
|
7
|
+
/** Option name */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Option name and aliases */
|
|
10
|
+
names: string[];
|
|
11
|
+
isBoolean?: boolean;
|
|
12
|
+
// `required` will be a boolean for options with brackets
|
|
13
|
+
required?: boolean;
|
|
14
|
+
config: OptionConfig;
|
|
15
|
+
negated: boolean;
|
|
16
|
+
constructor(public rawName: string, public description: string, config?: OptionConfig) {
|
|
17
|
+
this.config = Object.assign({}, config);
|
|
18
|
+
|
|
19
|
+
// You may use cli.option('--env.* [value]', 'desc') to denote a dot-nested option
|
|
20
|
+
rawName = rawName.replace(/\.\*/g, '');
|
|
21
|
+
this.negated = false;
|
|
22
|
+
this.names = removeBrackets(rawName).split(',').map((v: string) => {
|
|
23
|
+
let name = v.trim().replace(/^-{1,2}/, '');
|
|
24
|
+
if (name.startsWith('no-')) {
|
|
25
|
+
this.negated = true;
|
|
26
|
+
name = name.replace(/^no-/, '');
|
|
27
|
+
}
|
|
28
|
+
return camelcaseOptionName(name);
|
|
29
|
+
}).sort((a, b) => a.length > b.length ? 1 : -1); // Sort names
|
|
30
|
+
|
|
31
|
+
// Use the longest name (last one) as actual option name
|
|
32
|
+
this.name = this.names[this.names.length - 1];
|
|
33
|
+
if (this.negated && this.config.default == null) {
|
|
34
|
+
this.config.default = true;
|
|
35
|
+
}
|
|
36
|
+
if (rawName.includes('<')) {
|
|
37
|
+
this.required = true;
|
|
38
|
+
} else if (rawName.includes('[')) {
|
|
39
|
+
this.required = false;
|
|
40
|
+
} else {
|
|
41
|
+
// No arg needed, it's boolean flag
|
|
42
|
+
this.isBoolean = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export type { OptionConfig };
|
package/deno/deno.ts
ADDED
package/deno/index.ts
ADDED