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