@cloud-copilot/cli 0.1.39 → 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/README.md +86 -207
- package/dist/cjs/arguments/argument.d.ts +60 -0
- package/dist/cjs/arguments/argument.d.ts.map +1 -0
- package/dist/cjs/arguments/argument.js +3 -0
- package/dist/cjs/arguments/argument.js.map +1 -0
- package/dist/cjs/arguments/arrayValueArgument.d.ts +15 -0
- package/dist/cjs/arguments/arrayValueArgument.d.ts.map +1 -0
- package/dist/cjs/arguments/arrayValueArgument.js +38 -0
- package/dist/cjs/arguments/arrayValueArgument.js.map +1 -0
- package/dist/cjs/arguments/booleanArgument.d.ts +11 -0
- package/dist/cjs/arguments/booleanArgument.d.ts.map +1 -0
- package/dist/cjs/arguments/booleanArgument.js +25 -0
- package/dist/cjs/arguments/booleanArgument.js.map +1 -0
- package/dist/cjs/arguments/enumArgument.d.ts +15 -0
- package/dist/cjs/arguments/enumArgument.d.ts.map +1 -0
- package/dist/cjs/arguments/enumArgument.js +35 -0
- package/dist/cjs/arguments/enumArgument.js.map +1 -0
- package/dist/cjs/arguments/enumArrayArgument.d.ts +15 -0
- package/dist/cjs/arguments/enumArrayArgument.d.ts.map +1 -0
- package/dist/cjs/arguments/enumArrayArgument.js +42 -0
- package/dist/cjs/arguments/enumArrayArgument.js.map +1 -0
- package/dist/cjs/arguments/mapArgument.d.ts +18 -0
- package/dist/cjs/arguments/mapArgument.d.ts.map +1 -0
- package/dist/cjs/arguments/mapArgument.js +35 -0
- package/dist/cjs/arguments/mapArgument.js.map +1 -0
- package/dist/cjs/arguments/numberArguments.d.ts +17 -0
- package/dist/cjs/arguments/numberArguments.d.ts.map +1 -0
- package/dist/cjs/arguments/numberArguments.js +15 -0
- package/dist/cjs/arguments/numberArguments.js.map +1 -0
- package/dist/cjs/arguments/singleValueArgument.d.ts +15 -0
- package/dist/cjs/arguments/singleValueArgument.d.ts.map +1 -0
- package/dist/cjs/arguments/singleValueArgument.js +35 -0
- package/dist/cjs/arguments/singleValueArgument.js.map +1 -0
- package/dist/cjs/arguments/stringArguments.d.ts +17 -0
- package/dist/cjs/arguments/stringArguments.d.ts.map +1 -0
- package/dist/cjs/arguments/stringArguments.js +14 -0
- package/dist/cjs/arguments/stringArguments.js.map +1 -0
- package/dist/cjs/cli.d.ts +37 -89
- package/dist/cjs/cli.d.ts.map +1 -1
- package/dist/cjs/cli.js +171 -293
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/index.d.ts +11 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +20 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/readRelative.d.ts +25 -7
- package/dist/cjs/readRelative.d.ts.map +1 -1
- package/dist/cjs/readRelative.js +63 -20
- package/dist/cjs/readRelative.js.map +1 -1
- package/dist/esm/arguments/argument.d.ts +60 -0
- package/dist/esm/arguments/argument.d.ts.map +1 -0
- package/dist/esm/arguments/argument.js +2 -0
- package/dist/esm/arguments/argument.js.map +1 -0
- package/dist/esm/arguments/arrayValueArgument.d.ts +15 -0
- package/dist/esm/arguments/arrayValueArgument.d.ts.map +1 -0
- package/dist/esm/arguments/arrayValueArgument.js +35 -0
- package/dist/esm/arguments/arrayValueArgument.js.map +1 -0
- package/dist/esm/arguments/booleanArgument.d.ts +11 -0
- package/dist/esm/arguments/booleanArgument.d.ts.map +1 -0
- package/dist/esm/arguments/booleanArgument.js +22 -0
- package/dist/esm/arguments/booleanArgument.js.map +1 -0
- package/dist/esm/arguments/enumArgument.d.ts +15 -0
- package/dist/esm/arguments/enumArgument.d.ts.map +1 -0
- package/dist/esm/arguments/enumArgument.js +32 -0
- package/dist/esm/arguments/enumArgument.js.map +1 -0
- package/dist/esm/arguments/enumArrayArgument.d.ts +15 -0
- package/dist/esm/arguments/enumArrayArgument.d.ts.map +1 -0
- package/dist/esm/arguments/enumArrayArgument.js +39 -0
- package/dist/esm/arguments/enumArrayArgument.js.map +1 -0
- package/dist/esm/arguments/mapArgument.d.ts +18 -0
- package/dist/esm/arguments/mapArgument.d.ts.map +1 -0
- package/dist/esm/arguments/mapArgument.js +32 -0
- package/dist/esm/arguments/mapArgument.js.map +1 -0
- package/dist/esm/arguments/numberArguments.d.ts +17 -0
- package/dist/esm/arguments/numberArguments.d.ts.map +1 -0
- package/dist/esm/arguments/numberArguments.js +12 -0
- package/dist/esm/arguments/numberArguments.js.map +1 -0
- package/dist/esm/arguments/singleValueArgument.d.ts +15 -0
- package/dist/esm/arguments/singleValueArgument.d.ts.map +1 -0
- package/dist/esm/arguments/singleValueArgument.js +32 -0
- package/dist/esm/arguments/singleValueArgument.js.map +1 -0
- package/dist/esm/arguments/stringArguments.d.ts +17 -0
- package/dist/esm/arguments/stringArguments.d.ts.map +1 -0
- package/dist/esm/arguments/stringArguments.js +11 -0
- package/dist/esm/arguments/stringArguments.js.map +1 -0
- package/dist/esm/cli.d.ts +37 -89
- package/dist/esm/cli.d.ts.map +1 -1
- package/dist/esm/cli.js +171 -292
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/index.d.ts +11 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +10 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/readRelative.d.ts +25 -7
- package/dist/esm/readRelative.d.ts.map +1 -1
- package/dist/esm/readRelative.js +58 -19
- package/dist/esm/readRelative.js.map +1 -1
- package/package.json +2 -2
package/dist/esm/cli.js
CHANGED
|
@@ -1,120 +1,49 @@
|
|
|
1
1
|
import { exit } from './utils.js';
|
|
2
|
-
function isBooleanOption(option) {
|
|
3
|
-
return option.type === 'boolean';
|
|
4
|
-
}
|
|
5
|
-
function isEnumOption(option) {
|
|
6
|
-
return option.type === 'enum';
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Create a type safe configuration for CLI arguments.
|
|
10
|
-
*
|
|
11
|
-
* @param config the configuration for the CLI arguments.
|
|
12
|
-
* @returns the configuration object.
|
|
13
|
-
*/
|
|
14
|
-
export function createConfig(config) {
|
|
15
|
-
return config;
|
|
16
|
-
}
|
|
17
2
|
/**
|
|
18
3
|
* Parse CLI Arguments and return the parsed typesafe results.
|
|
19
4
|
*
|
|
20
5
|
* @param command the name of the command arguments are being parsed for.
|
|
21
6
|
* @param subcommands the list of subcommands that can be used, if any.
|
|
22
|
-
* @param
|
|
23
|
-
* @param
|
|
7
|
+
* @param cliArgs the configuration options for the CLI command.
|
|
8
|
+
* @param additionalOptions additional arguments to be used for parsing and displaying help.
|
|
24
9
|
* @returns the parsed arguments, operands, and subcommand if applicable.
|
|
25
10
|
*/
|
|
26
|
-
export function parseCliArguments(command, subcommands,
|
|
27
|
-
const args =
|
|
28
|
-
const env =
|
|
11
|
+
export async function parseCliArguments(command, subcommands, cliArgs, additionalOptions) {
|
|
12
|
+
const args = additionalOptions?.args ?? process.argv.slice(2);
|
|
13
|
+
const env = additionalOptions?.env ?? process.env;
|
|
29
14
|
const parsedArgs = {};
|
|
30
15
|
const operands = [];
|
|
31
16
|
const booleanOptions = {};
|
|
32
17
|
const subcommandKeys = Object.keys(subcommands);
|
|
33
18
|
const numberOfSubcommands = subcommandKeys.length;
|
|
34
|
-
const combinedOptions = { ...
|
|
19
|
+
const combinedOptions = { ...cliArgs };
|
|
20
|
+
const logger = additionalOptions?.consoleLogger ?? console;
|
|
35
21
|
let subcommand;
|
|
36
|
-
if (args.length === 0 &&
|
|
37
|
-
printHelpContents(command, subcommands,
|
|
22
|
+
if (args.length === 0 && additionalOptions?.showHelpIfNoArgs) {
|
|
23
|
+
printHelpContents(command, subcommands, cliArgs, additionalOptions);
|
|
38
24
|
exit(0, undefined);
|
|
39
25
|
return {};
|
|
40
26
|
}
|
|
41
27
|
// Step 1: Initialize defaults
|
|
42
|
-
|
|
28
|
+
const parsedEnvironmentArgs = {};
|
|
29
|
+
initializeOptionDefaults(parsedEnvironmentArgs, booleanOptions, cliArgs);
|
|
43
30
|
// Step 2: Handle environment variables
|
|
44
|
-
parseEnvironmentVariables(
|
|
45
|
-
if (additionalArgs?.envPrefix) {
|
|
46
|
-
const prefix = additionalArgs.envPrefix + '_';
|
|
47
|
-
const envToKeys = Object.keys(cliOptions).reduce((acc, key) => {
|
|
48
|
-
acc[camelToCapitalSnakeCase(key)] = key;
|
|
49
|
-
return acc;
|
|
50
|
-
}, {});
|
|
51
|
-
for (const [key, value] of Object.entries(env)) {
|
|
52
|
-
if (key.startsWith(prefix)) {
|
|
53
|
-
const optionKey = key.slice(prefix.length);
|
|
54
|
-
const option = envToKeys[optionKey];
|
|
55
|
-
if (option) {
|
|
56
|
-
const config = combinedOptions[option];
|
|
57
|
-
if (isEnumOption(config)) {
|
|
58
|
-
if (config.values === 'single') {
|
|
59
|
-
const matchingValue = config.validValues.find((v) => v.toLowerCase() === value.toLowerCase());
|
|
60
|
-
if (!matchingValue) {
|
|
61
|
-
exit(2, `Environment ${key} allows only the following values: ${config.validValues.join(', ')}`);
|
|
62
|
-
}
|
|
63
|
-
parsedArgs[option] = matchingValue;
|
|
64
|
-
}
|
|
65
|
-
else if (config.values === 'multiple') {
|
|
66
|
-
const invalidValues = [];
|
|
67
|
-
const validValues = [];
|
|
68
|
-
const values = value.split(' ');
|
|
69
|
-
for (const v of values) {
|
|
70
|
-
const matchingValue = config.validValues.find((valid) => valid.toLowerCase() === v.toLowerCase());
|
|
71
|
-
if (matchingValue) {
|
|
72
|
-
validValues.push(matchingValue);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
invalidValues.push(value);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (invalidValues.length > 0) {
|
|
79
|
-
exit(2, `Environment ${key} allows only the following values: ${config.validValues.join(', ')}`);
|
|
80
|
-
}
|
|
81
|
-
parsedArgs[option] = validValues;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
else if (config.type === 'boolean') {
|
|
85
|
-
parsedArgs[option] = true;
|
|
86
|
-
}
|
|
87
|
-
else if (config.values === 'single') {
|
|
88
|
-
const { parsed, invalid } = validateTypes(config.type, value);
|
|
89
|
-
if (invalid.length > 0) {
|
|
90
|
-
exit(2, `Environment ${key} expects a valid ${config.type}, but received: ${invalid.join(', ')}`);
|
|
91
|
-
}
|
|
92
|
-
parsedArgs[option] = parsed;
|
|
93
|
-
}
|
|
94
|
-
else if (config.values === 'multiple') {
|
|
95
|
-
const values = value.split(' ');
|
|
96
|
-
const { parsed, invalid } = validateTypes(config.type, values);
|
|
97
|
-
if (invalid.length > 0) {
|
|
98
|
-
exit(2, `Environment ${key} expects a valid ${config.type}, but received: ${invalid.join(', ')}`);
|
|
99
|
-
}
|
|
100
|
-
parsedArgs[option] = parsed;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
31
|
+
await parseEnvironmentVariables(cliArgs, parsedEnvironmentArgs, env, additionalOptions?.envPrefix);
|
|
106
32
|
// Step 3: Group arguments into objects
|
|
107
33
|
const commandChunks = groupArguments(args);
|
|
108
34
|
// Step 4: Validation and parsing arguments
|
|
109
35
|
for (const { first, rest, isLast, isFirst } of commandChunks) {
|
|
110
36
|
// Handle --help and --version
|
|
111
37
|
if (first === '--help') {
|
|
112
|
-
printHelpContents(command, subcommands,
|
|
38
|
+
printHelpContents(command, subcommands, cliArgs, additionalOptions, subcommand);
|
|
113
39
|
exit(0, undefined);
|
|
40
|
+
return {};
|
|
114
41
|
}
|
|
115
42
|
if (first === '--version') {
|
|
116
|
-
if (
|
|
117
|
-
|
|
43
|
+
if (additionalOptions?.version) {
|
|
44
|
+
await printVersion(additionalOptions.version, logger);
|
|
45
|
+
exit(0, undefined);
|
|
46
|
+
return {};
|
|
118
47
|
}
|
|
119
48
|
}
|
|
120
49
|
// Handle commands if applicable
|
|
@@ -141,9 +70,9 @@ export function parseCliArguments(command, subcommands, cliOptions, additionalAr
|
|
|
141
70
|
return {};
|
|
142
71
|
}
|
|
143
72
|
subcommand = matchingCommands.at(0);
|
|
144
|
-
const subcommandOptions = subcommands[subcommand].
|
|
145
|
-
initializeOptionDefaults(
|
|
146
|
-
parseEnvironmentVariables(subcommandOptions,
|
|
73
|
+
const subcommandOptions = subcommands[subcommand].arguments;
|
|
74
|
+
initializeOptionDefaults(parsedEnvironmentArgs, booleanOptions, subcommandOptions);
|
|
75
|
+
await parseEnvironmentVariables(subcommandOptions, parsedEnvironmentArgs, env, additionalOptions?.envPrefix);
|
|
147
76
|
for (const [key, option] of Object.entries(subcommandOptions)) {
|
|
148
77
|
combinedOptions[key] = option;
|
|
149
78
|
}
|
|
@@ -170,6 +99,16 @@ export function parseCliArguments(command, subcommands, cliOptions, additionalAr
|
|
|
170
99
|
matchingOption = exactMatch;
|
|
171
100
|
}
|
|
172
101
|
else {
|
|
102
|
+
if ('--help'.startsWith(first)) {
|
|
103
|
+
printHelpContents(command, subcommands, cliArgs, additionalOptions, subcommand);
|
|
104
|
+
exit(0, undefined);
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
else if ('--version'.startsWith(first) && additionalOptions?.version) {
|
|
108
|
+
await printVersion(additionalOptions.version, logger);
|
|
109
|
+
exit(0, undefined);
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
173
112
|
exit(2, `Unknown argument: ${first}`);
|
|
174
113
|
}
|
|
175
114
|
if (!matchingOption) {
|
|
@@ -177,93 +116,41 @@ export function parseCliArguments(command, subcommands, cliOptions, additionalAr
|
|
|
177
116
|
return {};
|
|
178
117
|
}
|
|
179
118
|
const optionConfig = combinedOptions[matchingOption];
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
exit(2, `Option ${first} expects at least one value, but received none`);
|
|
187
|
-
}
|
|
119
|
+
if (optionConfig.present) {
|
|
120
|
+
parsedArgs[matchingOption] = await optionConfig.present(parsedArgs[matchingOption]);
|
|
121
|
+
}
|
|
122
|
+
if (rest.length > 0 && optionConfig.character) {
|
|
123
|
+
if (!isLast) {
|
|
124
|
+
exit(2, `Validation error for ${first}: does not accept values but received ${rest.join(', ')}`);
|
|
188
125
|
return {};
|
|
189
126
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
exit(2, `Option ${first} expects a single value, but received multiple: ${rest.join(', ')}`);
|
|
193
|
-
}
|
|
194
|
-
const value = rest[0];
|
|
195
|
-
const matchingValue = optionConfig.validValues.find((v) => v.toLowerCase() === value.toLowerCase());
|
|
196
|
-
if (!matchingValue) {
|
|
197
|
-
exit(2, `Option ${first} allows only the following values: ${optionConfig.validValues.join(', ')}`);
|
|
198
|
-
}
|
|
199
|
-
parsedArgs[matchingOption] = matchingValue;
|
|
200
|
-
operands.push(...rest.slice(1));
|
|
201
|
-
}
|
|
202
|
-
else if (optionConfig.values === 'multiple') {
|
|
203
|
-
const invalidValues = [];
|
|
204
|
-
const validValues = [];
|
|
205
|
-
for (const value of rest) {
|
|
206
|
-
const matchingValue = optionConfig.validValues.find((v) => v.toLowerCase() === value.toLowerCase());
|
|
207
|
-
if (matchingValue) {
|
|
208
|
-
validValues.push(matchingValue);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
invalidValues.push(value);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (invalidValues.length > 0) {
|
|
215
|
-
exit(2, `Option ${first} allows only the following values: ${optionConfig.validValues.join(', ')}`);
|
|
216
|
-
}
|
|
217
|
-
parsedArgs[matchingOption] = validValues;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
else if (optionConfig.type === 'boolean') {
|
|
221
|
-
//set boolean value
|
|
222
|
-
parsedArgs[matchingOption] = true;
|
|
223
|
-
//Handle extra values
|
|
224
|
-
if (rest.length > 0) {
|
|
225
|
-
if (!isLast) {
|
|
226
|
-
exit(2, `Boolean option ${first} does not accept values`);
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
operands.push(...rest);
|
|
230
|
-
}
|
|
127
|
+
else {
|
|
128
|
+
operands.push(...rest);
|
|
231
129
|
}
|
|
232
130
|
}
|
|
233
|
-
else
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
//Set the value
|
|
243
|
-
parsedArgs[matchingOption] = parsed;
|
|
244
|
-
if (rest.length > 1) {
|
|
245
|
-
if (!isLast) {
|
|
246
|
-
exit(2, `Option ${first} expects a single value, but received multiple: ${rest.join(', ')}`);
|
|
131
|
+
else {
|
|
132
|
+
const acceptsMultiple = optionConfig.acceptMultipleValues
|
|
133
|
+
? optionConfig.acceptMultipleValues()
|
|
134
|
+
: false;
|
|
135
|
+
let theRest = rest;
|
|
136
|
+
if (!acceptsMultiple && rest.length > 1) {
|
|
137
|
+
if (isLast) {
|
|
138
|
+
theRest = [rest[0]];
|
|
139
|
+
operands.push(...rest.slice(1));
|
|
247
140
|
}
|
|
248
141
|
else {
|
|
249
|
-
|
|
142
|
+
exit(2, `Validation error for ${first}: expects a single value but received ${rest.join(', ')}`);
|
|
250
143
|
}
|
|
251
144
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (
|
|
255
|
-
exit(2, `
|
|
145
|
+
const currentValue = parsedArgs[matchingOption];
|
|
146
|
+
const validation = await optionConfig.validateValues(currentValue, theRest);
|
|
147
|
+
if (!validation.valid) {
|
|
148
|
+
exit(2, `Validation error for ${first}: ${validation.message}`);
|
|
149
|
+
return {};
|
|
256
150
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const { parsed, invalid } = validateTypes(optionConfig.type, rest);
|
|
260
|
-
if (invalid.length > 0) {
|
|
261
|
-
exit(2, `Option ${first} expects a valid ${optionConfig.type}, but received: ${invalid.join(', ')}`);
|
|
151
|
+
else {
|
|
152
|
+
parsedArgs[matchingOption] = await optionConfig.reduceValues(currentValue, validation.value);
|
|
262
153
|
}
|
|
263
|
-
parsedArgs[matchingOption] = parsed;
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
throw new Error(`Unrecognized option values ${optionConfig.values}`);
|
|
267
154
|
}
|
|
268
155
|
}
|
|
269
156
|
else if (first.startsWith('-')) {
|
|
@@ -284,56 +171,52 @@ export function parseCliArguments(command, subcommands, cliOptions, additionalAr
|
|
|
284
171
|
}
|
|
285
172
|
}
|
|
286
173
|
}
|
|
287
|
-
if (numberOfSubcommands > 0 &&
|
|
174
|
+
if (numberOfSubcommands > 0 && additionalOptions?.requireSubcommand && !subcommand) {
|
|
288
175
|
exit(2, `A subcommand is required`);
|
|
289
176
|
}
|
|
290
177
|
// Step 4: Return results
|
|
291
178
|
return {
|
|
292
|
-
args: parsedArgs,
|
|
179
|
+
args: { ...parsedEnvironmentArgs, ...parsedArgs },
|
|
293
180
|
operands,
|
|
294
181
|
subcommand: subcommand,
|
|
295
182
|
anyValues: args.length > 0,
|
|
296
183
|
printHelp: () => {
|
|
297
|
-
printHelpContents(command, subcommands,
|
|
184
|
+
printHelpContents(command, subcommands, cliArgs, additionalOptions, subcommand);
|
|
298
185
|
}
|
|
299
186
|
};
|
|
300
187
|
}
|
|
301
188
|
/**
|
|
302
|
-
* Initialize the default values for arguments
|
|
189
|
+
* Initialize the default values for arguments.
|
|
190
|
+
*
|
|
191
|
+
* Will populate the parsedArgs object with default values from the cliArguments.
|
|
192
|
+
* Will also populate the booleanOptions map with single character boolean options.
|
|
303
193
|
*
|
|
304
194
|
* @param parsedArgs the parsed arguments to default the values in
|
|
305
|
-
* @param
|
|
195
|
+
* @param booleanOptions a map of single character boolean options to their full names
|
|
196
|
+
* @param cliArguments the configuration options for the CLI commands
|
|
306
197
|
*/
|
|
307
|
-
function initializeOptionDefaults(parsedArgs, booleanOptions,
|
|
308
|
-
for (const [key, option] of Object.entries(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
booleanOptions[option.character.toLowerCase()] = key;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
else if (option.values === 'single') {
|
|
316
|
-
parsedArgs[key] = undefined;
|
|
317
|
-
}
|
|
318
|
-
else if (option.values === 'multiple') {
|
|
319
|
-
parsedArgs[key] = [];
|
|
198
|
+
function initializeOptionDefaults(parsedArgs, booleanOptions, cliArguments) {
|
|
199
|
+
for (const [key, option] of Object.entries(cliArguments)) {
|
|
200
|
+
parsedArgs[key] = option.defaultValue;
|
|
201
|
+
if (option.character) {
|
|
202
|
+
booleanOptions[option.character.toLowerCase()] = key;
|
|
320
203
|
}
|
|
321
204
|
}
|
|
322
205
|
}
|
|
323
206
|
/**
|
|
324
207
|
* Parse environment variables and set the values in the parsed arguments.
|
|
325
208
|
*
|
|
326
|
-
* @param
|
|
209
|
+
* @param cliArguments the configuration options for the CLI commands
|
|
327
210
|
* @param parsedArgs the parsed arguments to set the values in
|
|
328
211
|
* @param env the environment variables to get values from
|
|
329
212
|
* @param envPrefix the prefix to use for environment variables, if any
|
|
330
213
|
*/
|
|
331
|
-
function parseEnvironmentVariables(
|
|
214
|
+
async function parseEnvironmentVariables(cliArguments, parsedArgs, env, envPrefix) {
|
|
332
215
|
if (!envPrefix) {
|
|
333
216
|
return;
|
|
334
217
|
}
|
|
335
218
|
const prefix = envPrefix + '_';
|
|
336
|
-
const envToKeys = Object.keys(
|
|
219
|
+
const envToKeys = Object.keys(cliArguments).reduce((acc, key) => {
|
|
337
220
|
acc[camelToCapitalSnakeCase(key)] = key;
|
|
338
221
|
return acc;
|
|
339
222
|
}, {});
|
|
@@ -342,51 +225,19 @@ function parseEnvironmentVariables(options, parsedArgs, env, envPrefix) {
|
|
|
342
225
|
const optionKey = key.slice(prefix.length);
|
|
343
226
|
const option = envToKeys[optionKey];
|
|
344
227
|
if (option) {
|
|
345
|
-
const config =
|
|
346
|
-
if (
|
|
347
|
-
|
|
348
|
-
const matchingValue = config.validValues.find((v) => v.toLowerCase() === value.toLowerCase());
|
|
349
|
-
if (!matchingValue) {
|
|
350
|
-
exit(2, `Environment ${key} allows only the following values: ${config.validValues.join(', ')}`);
|
|
351
|
-
}
|
|
352
|
-
parsedArgs[option] = matchingValue;
|
|
353
|
-
}
|
|
354
|
-
else if (config.values === 'multiple') {
|
|
355
|
-
const invalidValues = [];
|
|
356
|
-
const validValues = [];
|
|
357
|
-
const values = value.split(' ');
|
|
358
|
-
for (const v of values) {
|
|
359
|
-
const matchingValue = config.validValues.find((valid) => valid.toLowerCase() === v.toLowerCase());
|
|
360
|
-
if (matchingValue) {
|
|
361
|
-
validValues.push(matchingValue);
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
invalidValues.push(value);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
if (invalidValues.length > 0) {
|
|
368
|
-
exit(2, `Environment ${key} allows only the following values: ${config.validValues.join(', ')}`);
|
|
369
|
-
}
|
|
370
|
-
parsedArgs[option] = validValues;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
else if (config.type === 'boolean') {
|
|
374
|
-
parsedArgs[option] = true;
|
|
375
|
-
}
|
|
376
|
-
else if (config.values === 'single') {
|
|
377
|
-
const { parsed, invalid } = validateTypes(config.type, value);
|
|
378
|
-
if (invalid.length > 0) {
|
|
379
|
-
exit(2, `Environment ${key} expects a valid ${config.type}, but received: ${invalid.join(', ')}`);
|
|
380
|
-
}
|
|
381
|
-
parsedArgs[option] = parsed;
|
|
228
|
+
const config = cliArguments[option];
|
|
229
|
+
if (config.present) {
|
|
230
|
+
parsedArgs[option] = await config.present(parsedArgs[option]);
|
|
382
231
|
}
|
|
383
|
-
|
|
232
|
+
if (!config.character) {
|
|
384
233
|
const values = value.split(' ');
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
|
|
234
|
+
const validation = await config.validateValues(parsedArgs[option], values);
|
|
235
|
+
if (!validation.valid) {
|
|
236
|
+
const s = values.length > 1 ? 's' : '';
|
|
237
|
+
exit(2, `Invalid value${s} for environment ${key}: ${validation.message}`);
|
|
238
|
+
return;
|
|
388
239
|
}
|
|
389
|
-
parsedArgs[option] =
|
|
240
|
+
parsedArgs[option] = await config.reduceValues(parsedArgs[option], validation.value);
|
|
390
241
|
}
|
|
391
242
|
}
|
|
392
243
|
}
|
|
@@ -433,30 +284,6 @@ function groupArguments(args) {
|
|
|
433
284
|
}
|
|
434
285
|
return grouped;
|
|
435
286
|
}
|
|
436
|
-
/**
|
|
437
|
-
* Validate the types of a standard argument
|
|
438
|
-
*
|
|
439
|
-
* @param type the type the argument accepts
|
|
440
|
-
* @param values the values to validate
|
|
441
|
-
* @returns an object with the invalid values and the parsed values
|
|
442
|
-
*/
|
|
443
|
-
function validateTypes(type, values) {
|
|
444
|
-
if (type === 'string') {
|
|
445
|
-
return { invalid: [], parsed: values };
|
|
446
|
-
}
|
|
447
|
-
if (!Array.isArray(values)) {
|
|
448
|
-
const isValid = !isNaN(Number(values));
|
|
449
|
-
if (isValid) {
|
|
450
|
-
return { invalid: [], parsed: Number(values) };
|
|
451
|
-
}
|
|
452
|
-
return { invalid: [values], parsed: 0 };
|
|
453
|
-
}
|
|
454
|
-
const invalid = values.filter((v) => isNaN(Number(v)));
|
|
455
|
-
if (invalid.length > 0) {
|
|
456
|
-
return { invalid, parsed: [] };
|
|
457
|
-
}
|
|
458
|
-
return { invalid: [], parsed: values.map(Number) };
|
|
459
|
-
}
|
|
460
287
|
function camelToCapitalSnakeCase(input) {
|
|
461
288
|
return input
|
|
462
289
|
.replace(/([a-z])([A-Z])/g, '$1_$2') // Insert underscore before capital letters
|
|
@@ -468,25 +295,27 @@ function camelToKebabCase(input) {
|
|
|
468
295
|
.toLowerCase(); // Convert to uppercase
|
|
469
296
|
}
|
|
470
297
|
export function printHelpContents(command, subcommands, cliOptions, additionalArgs, selectedSubcommand) {
|
|
298
|
+
const logger = additionalArgs?.consoleLogger ?? console;
|
|
471
299
|
const operandsExpected = additionalArgs?.expectOperands != undefined ? additionalArgs?.expectOperands : true;
|
|
472
300
|
const operandsName = additionalArgs?.operandsName ?? 'operand';
|
|
473
301
|
const operandsString = operandsExpected ? ` [--] [${operandsName}1] [${operandsName}2]` : '';
|
|
474
|
-
const anyGlobalFlags = Object.values(cliOptions).some((option) =>
|
|
302
|
+
const anyGlobalFlags = Object.values(cliOptions).some((option) => option.character);
|
|
475
303
|
if (selectedSubcommand) {
|
|
476
|
-
const anyCommandFlags = Object.values(subcommands[selectedSubcommand].
|
|
304
|
+
const anyCommandFlags = Object.values(subcommands[selectedSubcommand].arguments).some((option) => option.character);
|
|
477
305
|
const flags = anyGlobalFlags || anyCommandFlags ? ' [flags]' : '';
|
|
478
306
|
let usageString = `Usage: ${command} ${selectedSubcommand} [options]${flags}${operandsString}`;
|
|
479
307
|
if (additionalArgs?.allowOperandsFromStdin) {
|
|
480
308
|
usageString += `\n <${operandsName}s to stdout> | ${command} ${selectedSubcommand} [options]${flags}`;
|
|
481
309
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
310
|
+
logger.log(usageString);
|
|
311
|
+
logger.log('');
|
|
312
|
+
logger.log(`${subcommands[selectedSubcommand].description}`);
|
|
313
|
+
logger.log(`${selectedSubcommand} Options:`);
|
|
314
|
+
printOptions(subcommands[selectedSubcommand].arguments, logger);
|
|
315
|
+
logger.log('');
|
|
487
316
|
}
|
|
488
317
|
else {
|
|
489
|
-
const anyCommandFlags = Object.values(subcommands).some((subcommand) => Object.values(subcommand.
|
|
318
|
+
const anyCommandFlags = Object.values(subcommands).some((subcommand) => Object.values(subcommand.arguments).some((option) => option.character));
|
|
490
319
|
const flags = anyGlobalFlags || anyCommandFlags ? ' [flags]' : '';
|
|
491
320
|
let singleUseString = `${command}`;
|
|
492
321
|
const subcommandKeys = Object.keys(subcommands);
|
|
@@ -501,18 +330,19 @@ export function printHelpContents(command, subcommands, cliOptions, additionalAr
|
|
|
501
330
|
if (additionalArgs?.allowOperandsFromStdin) {
|
|
502
331
|
usageString += `\n <${operandsName}s to stdout> | ${singleUseString}`;
|
|
503
332
|
}
|
|
504
|
-
|
|
333
|
+
logger.log(usageString);
|
|
505
334
|
const longestCommand = subcommandKeys.reduce((acc, cmd) => Math.max(acc, cmd.length), 0);
|
|
506
335
|
if (subcommandKeys.length > 0) {
|
|
507
|
-
|
|
336
|
+
logger.log('Subcommands:');
|
|
508
337
|
for (const cmd of subcommandKeys) {
|
|
509
338
|
const description = subcommands[cmd].description;
|
|
510
|
-
|
|
339
|
+
logger.log(` ${(cmd + ':').padEnd(longestCommand + 1)} ${description}`);
|
|
511
340
|
}
|
|
512
|
-
|
|
341
|
+
logger.log('');
|
|
342
|
+
logger.log(` Use ${command} <subcommand> --help for more information about a subcommand`);
|
|
513
343
|
}
|
|
514
344
|
}
|
|
515
|
-
|
|
345
|
+
logger.log('Global Options:');
|
|
516
346
|
const globalOptions = {
|
|
517
347
|
...cliOptions,
|
|
518
348
|
...{
|
|
@@ -526,46 +356,95 @@ export function printHelpContents(command, subcommands, cliOptions, additionalAr
|
|
|
526
356
|
description: 'Print the version and exit'
|
|
527
357
|
};
|
|
528
358
|
}
|
|
529
|
-
printOptions(globalOptions);
|
|
359
|
+
printOptions(globalOptions, logger);
|
|
530
360
|
}
|
|
531
|
-
function printOptions(cliOptions) {
|
|
361
|
+
function printOptions(cliOptions, logger) {
|
|
532
362
|
const longestOption = Object.keys(cliOptions).reduce((acc, key) => Math.max(acc, camelToKebabCase(key).length + 2), 0) + 1;
|
|
533
363
|
const terminalWidth = process.stdout.columns ?? 80;
|
|
534
364
|
const nonBooleanBuffer = ' ';
|
|
535
365
|
for (const [key, option] of Object.entries(cliOptions)) {
|
|
536
366
|
let optionString = ` --${camelToKebabCase(key)}:`.padEnd(longestOption + 3);
|
|
537
|
-
if (
|
|
367
|
+
if (option.character) {
|
|
538
368
|
optionString += `(-${option.character}) `;
|
|
539
369
|
}
|
|
540
370
|
else {
|
|
541
371
|
optionString += nonBooleanBuffer;
|
|
542
372
|
}
|
|
543
373
|
const leftBar = optionString.length;
|
|
544
|
-
optionString += option.description
|
|
545
|
-
|
|
546
|
-
if (option.values === 'single') {
|
|
547
|
-
optionString += `Must be one of: ${option.validValues.join(', ')}.`;
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
optionString += `Valid values: ${option.validValues.join(', ')}.`;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
else if (option.type === 'boolean') {
|
|
554
|
-
// Do nothing
|
|
555
|
-
}
|
|
556
|
-
else if (option.values === 'single') {
|
|
557
|
-
optionString += `One ${option.type} required`;
|
|
558
|
-
}
|
|
559
|
-
else if (option.values === 'multiple') {
|
|
560
|
-
optionString += `Multiple ${option.type}s allowed`;
|
|
561
|
-
}
|
|
562
|
-
console.log(optionString.slice(0, terminalWidth));
|
|
374
|
+
optionString += option.description;
|
|
375
|
+
logger.log(optionString.slice(0, terminalWidth));
|
|
563
376
|
let stringToPrint = optionString.slice(terminalWidth);
|
|
564
377
|
const secondLineLength = terminalWidth - leftBar;
|
|
565
378
|
while (stringToPrint.length > 0) {
|
|
566
|
-
|
|
379
|
+
logger.log(' '.repeat(leftBar) + stringToPrint.slice(0, secondLineLength).trimStart());
|
|
567
380
|
stringToPrint = stringToPrint.slice(secondLineLength);
|
|
568
381
|
}
|
|
569
382
|
}
|
|
570
383
|
}
|
|
384
|
+
async function printVersion(versionInfo, logger) {
|
|
385
|
+
if (!versionInfo) {
|
|
386
|
+
logger.log('Version information not available. This is a bug.');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (typeof versionInfo === 'string') {
|
|
390
|
+
logger.log(versionInfo);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
let currentVersion = null;
|
|
394
|
+
if (typeof versionInfo.currentVersion === 'string') {
|
|
395
|
+
currentVersion = versionInfo.currentVersion;
|
|
396
|
+
}
|
|
397
|
+
else if (typeof versionInfo.currentVersion === 'function') {
|
|
398
|
+
currentVersion = await versionInfo.currentVersion();
|
|
399
|
+
}
|
|
400
|
+
if (!currentVersion) {
|
|
401
|
+
logger.log('Current version not available');
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
let latestVersion = null;
|
|
405
|
+
if (typeof versionInfo.checkForUpdates == 'string') {
|
|
406
|
+
latestVersion = await getLatestVersionFromNpm(versionInfo.checkForUpdates);
|
|
407
|
+
}
|
|
408
|
+
else if (typeof versionInfo.checkForUpdates === 'function') {
|
|
409
|
+
latestVersion = await versionInfo.checkForUpdates();
|
|
410
|
+
}
|
|
411
|
+
let updateMessage = undefined;
|
|
412
|
+
if (currentVersion && latestVersion && currentVersion !== latestVersion) {
|
|
413
|
+
if (versionInfo.updateMessage) {
|
|
414
|
+
updateMessage = versionInfo.updateMessage(currentVersion, latestVersion);
|
|
415
|
+
}
|
|
416
|
+
else if (typeof versionInfo.checkForUpdates === 'string') {
|
|
417
|
+
updateMessage = `Latest: ${latestVersion}. To update run: npm update -g ${versionInfo.checkForUpdates}`;
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
updateMessage = `Latest: ${latestVersion}.`;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
else if (currentVersion && latestVersion && currentVersion === latestVersion) {
|
|
424
|
+
// updateMessage = 'You are using the latest version.'
|
|
425
|
+
}
|
|
426
|
+
logger.log(currentVersion);
|
|
427
|
+
if (updateMessage) {
|
|
428
|
+
logger.log(updateMessage);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Fetch the latest version of a package from the npm registry.
|
|
433
|
+
*
|
|
434
|
+
* @param packageName the name of the npm package to check, e.g. "my-cli-tool" or "@my-org/my-cli-tool"
|
|
435
|
+
* @returns the latest version of the package published on npm or null if any error occurs
|
|
436
|
+
*/
|
|
437
|
+
async function getLatestVersionFromNpm(packageName) {
|
|
438
|
+
try {
|
|
439
|
+
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
440
|
+
if (res.ok) {
|
|
441
|
+
const data = await res.json();
|
|
442
|
+
return data.version || null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch (e) {
|
|
446
|
+
// Ignore errors fetching latest version
|
|
447
|
+
}
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
571
450
|
//# sourceMappingURL=cli.js.map
|