@cloud-copilot/cli 0.1.39 → 0.2.1

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.
Files changed (98) hide show
  1. package/README.md +86 -207
  2. package/dist/cjs/arguments/argument.d.ts +60 -0
  3. package/dist/cjs/arguments/argument.d.ts.map +1 -0
  4. package/dist/cjs/arguments/argument.js +3 -0
  5. package/dist/cjs/arguments/argument.js.map +1 -0
  6. package/dist/cjs/arguments/arrayValueArgument.d.ts +15 -0
  7. package/dist/cjs/arguments/arrayValueArgument.d.ts.map +1 -0
  8. package/dist/cjs/arguments/arrayValueArgument.js +38 -0
  9. package/dist/cjs/arguments/arrayValueArgument.js.map +1 -0
  10. package/dist/cjs/arguments/booleanArgument.d.ts +11 -0
  11. package/dist/cjs/arguments/booleanArgument.d.ts.map +1 -0
  12. package/dist/cjs/arguments/booleanArgument.js +25 -0
  13. package/dist/cjs/arguments/booleanArgument.js.map +1 -0
  14. package/dist/cjs/arguments/enumArgument.d.ts +15 -0
  15. package/dist/cjs/arguments/enumArgument.d.ts.map +1 -0
  16. package/dist/cjs/arguments/enumArgument.js +35 -0
  17. package/dist/cjs/arguments/enumArgument.js.map +1 -0
  18. package/dist/cjs/arguments/enumArrayArgument.d.ts +15 -0
  19. package/dist/cjs/arguments/enumArrayArgument.d.ts.map +1 -0
  20. package/dist/cjs/arguments/enumArrayArgument.js +42 -0
  21. package/dist/cjs/arguments/enumArrayArgument.js.map +1 -0
  22. package/dist/cjs/arguments/mapArgument.d.ts +18 -0
  23. package/dist/cjs/arguments/mapArgument.d.ts.map +1 -0
  24. package/dist/cjs/arguments/mapArgument.js +35 -0
  25. package/dist/cjs/arguments/mapArgument.js.map +1 -0
  26. package/dist/cjs/arguments/numberArguments.d.ts +17 -0
  27. package/dist/cjs/arguments/numberArguments.d.ts.map +1 -0
  28. package/dist/cjs/arguments/numberArguments.js +15 -0
  29. package/dist/cjs/arguments/numberArguments.js.map +1 -0
  30. package/dist/cjs/arguments/singleValueArgument.d.ts +15 -0
  31. package/dist/cjs/arguments/singleValueArgument.d.ts.map +1 -0
  32. package/dist/cjs/arguments/singleValueArgument.js +35 -0
  33. package/dist/cjs/arguments/singleValueArgument.js.map +1 -0
  34. package/dist/cjs/arguments/stringArguments.d.ts +17 -0
  35. package/dist/cjs/arguments/stringArguments.d.ts.map +1 -0
  36. package/dist/cjs/arguments/stringArguments.js +14 -0
  37. package/dist/cjs/arguments/stringArguments.js.map +1 -0
  38. package/dist/cjs/cli.d.ts +37 -89
  39. package/dist/cjs/cli.d.ts.map +1 -1
  40. package/dist/cjs/cli.js +171 -293
  41. package/dist/cjs/cli.js.map +1 -1
  42. package/dist/cjs/index.d.ts +11 -2
  43. package/dist/cjs/index.d.ts.map +1 -1
  44. package/dist/cjs/index.js +20 -3
  45. package/dist/cjs/index.js.map +1 -1
  46. package/dist/cjs/readRelative.d.ts +25 -7
  47. package/dist/cjs/readRelative.d.ts.map +1 -1
  48. package/dist/cjs/readRelative.js +63 -20
  49. package/dist/cjs/readRelative.js.map +1 -1
  50. package/dist/esm/arguments/argument.d.ts +60 -0
  51. package/dist/esm/arguments/argument.d.ts.map +1 -0
  52. package/dist/esm/arguments/argument.js +2 -0
  53. package/dist/esm/arguments/argument.js.map +1 -0
  54. package/dist/esm/arguments/arrayValueArgument.d.ts +15 -0
  55. package/dist/esm/arguments/arrayValueArgument.d.ts.map +1 -0
  56. package/dist/esm/arguments/arrayValueArgument.js +35 -0
  57. package/dist/esm/arguments/arrayValueArgument.js.map +1 -0
  58. package/dist/esm/arguments/booleanArgument.d.ts +11 -0
  59. package/dist/esm/arguments/booleanArgument.d.ts.map +1 -0
  60. package/dist/esm/arguments/booleanArgument.js +22 -0
  61. package/dist/esm/arguments/booleanArgument.js.map +1 -0
  62. package/dist/esm/arguments/enumArgument.d.ts +15 -0
  63. package/dist/esm/arguments/enumArgument.d.ts.map +1 -0
  64. package/dist/esm/arguments/enumArgument.js +32 -0
  65. package/dist/esm/arguments/enumArgument.js.map +1 -0
  66. package/dist/esm/arguments/enumArrayArgument.d.ts +15 -0
  67. package/dist/esm/arguments/enumArrayArgument.d.ts.map +1 -0
  68. package/dist/esm/arguments/enumArrayArgument.js +39 -0
  69. package/dist/esm/arguments/enumArrayArgument.js.map +1 -0
  70. package/dist/esm/arguments/mapArgument.d.ts +18 -0
  71. package/dist/esm/arguments/mapArgument.d.ts.map +1 -0
  72. package/dist/esm/arguments/mapArgument.js +32 -0
  73. package/dist/esm/arguments/mapArgument.js.map +1 -0
  74. package/dist/esm/arguments/numberArguments.d.ts +17 -0
  75. package/dist/esm/arguments/numberArguments.d.ts.map +1 -0
  76. package/dist/esm/arguments/numberArguments.js +12 -0
  77. package/dist/esm/arguments/numberArguments.js.map +1 -0
  78. package/dist/esm/arguments/singleValueArgument.d.ts +15 -0
  79. package/dist/esm/arguments/singleValueArgument.d.ts.map +1 -0
  80. package/dist/esm/arguments/singleValueArgument.js +32 -0
  81. package/dist/esm/arguments/singleValueArgument.js.map +1 -0
  82. package/dist/esm/arguments/stringArguments.d.ts +17 -0
  83. package/dist/esm/arguments/stringArguments.d.ts.map +1 -0
  84. package/dist/esm/arguments/stringArguments.js +11 -0
  85. package/dist/esm/arguments/stringArguments.js.map +1 -0
  86. package/dist/esm/cli.d.ts +37 -89
  87. package/dist/esm/cli.d.ts.map +1 -1
  88. package/dist/esm/cli.js +171 -292
  89. package/dist/esm/cli.js.map +1 -1
  90. package/dist/esm/index.d.ts +11 -2
  91. package/dist/esm/index.d.ts.map +1 -1
  92. package/dist/esm/index.js +10 -2
  93. package/dist/esm/index.js.map +1 -1
  94. package/dist/esm/readRelative.d.ts +25 -7
  95. package/dist/esm/readRelative.d.ts.map +1 -1
  96. package/dist/esm/readRelative.js +58 -19
  97. package/dist/esm/readRelative.js.map +1 -1
  98. package/package.json +1 -1
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 cliOptions the configuration options for the CLI command.
23
- * @param additionalArgs additional arguments to be used for parsing and displaying help.
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, cliOptions, additionalArgs) {
27
- const args = additionalArgs?.args ?? process.argv.slice(2);
28
- const env = additionalArgs?.env ?? process.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 = { ...cliOptions };
19
+ const combinedOptions = { ...cliArgs };
20
+ const logger = additionalOptions?.consoleLogger ?? console;
35
21
  let subcommand;
36
- if (args.length === 0 && additionalArgs?.showHelpIfNoArgs) {
37
- printHelpContents(command, subcommands, cliOptions, additionalArgs);
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
- initializeOptionDefaults(parsedArgs, booleanOptions, cliOptions);
28
+ const parsedEnvironmentArgs = {};
29
+ initializeOptionDefaults(parsedEnvironmentArgs, booleanOptions, cliArgs);
43
30
  // Step 2: Handle environment variables
44
- parseEnvironmentVariables(cliOptions, parsedArgs, env, additionalArgs?.envPrefix);
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, cliOptions, additionalArgs, subcommand);
38
+ printHelpContents(command, subcommands, cliArgs, additionalOptions, subcommand);
113
39
  exit(0, undefined);
40
+ return {};
114
41
  }
115
42
  if (first === '--version') {
116
- if (additionalArgs?.version) {
117
- exit(0, additionalArgs?.version);
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].options;
145
- initializeOptionDefaults(parsedArgs, booleanOptions, subcommandOptions);
146
- parseEnvironmentVariables(subcommandOptions, parsedArgs, env, additionalArgs?.envPrefix);
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 (isEnumOption(optionConfig)) {
181
- if (rest.length === 0) {
182
- if (optionConfig.values === 'single') {
183
- exit(2, `Option ${first} expects a value, but received none`);
184
- }
185
- else {
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
- if (optionConfig.values === 'single') {
191
- if (rest.length > 1 && !isLast) {
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 if (optionConfig.values === 'single') {
234
- if (rest.length === 0) {
235
- exit(2, `Option ${first} expects a value, but received none`);
236
- }
237
- //Validate the value
238
- const { parsed, invalid } = validateTypes(optionConfig.type, rest[0]);
239
- if (invalid.length > 0) {
240
- exit(2, `Option ${first} expects a valid ${optionConfig.type}, but received: ${invalid.join(', ')}`);
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
- operands.push(...rest.slice(1));
142
+ exit(2, `Validation error for ${first}: expects a single value but received ${rest.join(', ')}`);
250
143
  }
251
144
  }
252
- }
253
- else if (optionConfig.values === 'multiple') {
254
- if (rest.length === 0) {
255
- exit(2, `Option ${first} expects at least one value, but received none`);
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
- //Set the Value
258
- //Validate the value
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 && additionalArgs?.requireSubcommand && !subcommand) {
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, cliOptions, additionalArgs, subcommand);
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 cliOptions the configuration options for the CLI commands
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, cliOptions) {
308
- for (const [key, option] of Object.entries(cliOptions)) {
309
- if (option.type === 'boolean') {
310
- parsedArgs[key] = false; //(option as BooleanOption).default
311
- if (option.character) {
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 options the configuration options for the CLI commands
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(options, parsedArgs, env, envPrefix) {
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(options).reduce((acc, key) => {
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 = options[option];
346
- if (isEnumOption(config)) {
347
- if (config.values === 'single') {
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
- else if (config.values === 'multiple') {
232
+ if (!config.character) {
384
233
  const values = value.split(' ');
385
- const { parsed, invalid } = validateTypes(config.type, values);
386
- if (invalid.length > 0) {
387
- exit(2, `Environment ${key} expects a valid ${config.type}, but received: ${invalid.join(', ')}`);
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] = parsed;
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) => isBooleanOption(option));
302
+ const anyGlobalFlags = Object.values(cliOptions).some((option) => option.character);
475
303
  if (selectedSubcommand) {
476
- const anyCommandFlags = Object.values(subcommands[selectedSubcommand].options).some((option) => isBooleanOption(option));
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
- console.log(usageString);
483
- console.log(`\n${subcommands[selectedSubcommand].description}`);
484
- console.log(`${selectedSubcommand} Options:`);
485
- printOptions(subcommands[selectedSubcommand].options);
486
- console.log('');
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.options).some((option) => isBooleanOption(option)));
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
- console.log(usageString);
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
- console.log('Subcommands:');
336
+ logger.log('Subcommands:');
508
337
  for (const cmd of subcommandKeys) {
509
338
  const description = subcommands[cmd].description;
510
- console.log(` ${(cmd + ':').padEnd(longestCommand + 1)} ${description}`);
339
+ logger.log(` ${(cmd + ':').padEnd(longestCommand + 1)} ${description}`);
511
340
  }
512
- console.log(`\n Use ${command} <subcommand> --help for more information about a subcommand`);
341
+ logger.log('');
342
+ logger.log(` Use ${command} <subcommand> --help for more information about a subcommand`);
513
343
  }
514
344
  }
515
- console.log('Global Options:');
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 (isBooleanOption(option)) {
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
- if (isEnumOption(option)) {
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
- console.log(' '.repeat(leftBar) + stringToPrint.slice(0, secondLineLength));
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