@bemoje/cli 0.0.2 → 0.0.4

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 (190) hide show
  1. package/index.cjs.js +2314 -1765
  2. package/index.esm.js +2222 -1661
  3. package/package.json +5 -3
  4. package/src/{core/CommandBuilder → arg}/ArgumentBuilder.d.ts +5 -6
  5. package/src/arg/ArgumentParserSelector.d.ts +8 -0
  6. package/src/{core/CommandBuilder → arg}/ArgumentReader.d.ts +1 -2
  7. package/src/arg/ArgumentValidatorSelector.d.ts +7 -0
  8. package/src/cmd/CLI.d.ts +2 -0
  9. package/src/cmd/CommandBuilder.d.ts +238 -0
  10. package/src/cmd/CommandBuilderMetaData.d.ts +22 -0
  11. package/src/cmd/CommandFeatureSelector.d.ts +79 -0
  12. package/src/cmd/DefaultHelpConfig.d.ts +2 -0
  13. package/src/core/OutputManager.d.ts +63 -0
  14. package/src/{parsers/selector/AbstractStringParserSelector.d.ts → core/ParserSelector.d.ts} +5 -6
  15. package/src/core/ValidatorSelector.d.ts +16 -0
  16. package/src/db/AbstractJsonFileSection.d.ts +136 -0
  17. package/src/db/AppDataSection.d.ts +29 -0
  18. package/src/db/ConfigSection.d.ts +47 -0
  19. package/src/db/JsonFile.d.ts +35 -0
  20. package/src/db/PresetsSection.d.ts +44 -0
  21. package/src/index.d.ts +101 -112
  22. package/src/opt/OptionArgumentParserSelector.d.ts +7 -0
  23. package/src/opt/OptionArgumentValidatorSelector.d.ts +7 -0
  24. package/src/{core/CommandBuilder → opt}/OptionBuilder.d.ts +6 -5
  25. package/src/opt/OptionHelpers.d.ts +32 -0
  26. package/src/{core/CommandBuilder → opt}/OptionReader.d.ts +2 -3
  27. package/src/proto/overrideCommanderPrototype.d.ts +8 -0
  28. package/src/types/IConfig.d.ts +21 -0
  29. package/src/types/IPreset.d.ts +6 -7
  30. package/src/util/array/arrLast.d.ts +12 -0
  31. package/src/util/array/arrSome.d.ts +15 -0
  32. package/src/util/array/types/ArrayPredicate.d.ts +10 -0
  33. package/src/util/db/JsonDB.d.ts +65 -0
  34. package/src/util/fs/promptUserEditInTextEditor/IGetUserInputFromEditorOptions.d.ts +15 -0
  35. package/src/util/fs/promptUserEditInTextEditor/promptUserEditInTextEditorSync.d.ts +9 -0
  36. package/src/util/fs/promptUserEditInTextEditor/promptUserEditJsonInTextEditorSync.d.ts +13 -0
  37. package/src/util/fs/readFile/readFileSafeSync.d.ts +13 -0
  38. package/src/util/fs/readFile/readFileSync.d.ts +10 -0
  39. package/src/util/fs/readJsonFile/readJsonFileSafeSync.d.ts +9 -0
  40. package/src/util/fs/removeFile/removeFile.d.ts +1 -0
  41. package/src/util/fs/tempFile/tempFileSync.d.ts +7 -0
  42. package/src/util/fs/types/IReadJsonFileOptions.d.ts +1 -0
  43. package/src/util/fs/writeFile/writeFileSafeSync.d.ts +1 -0
  44. package/src/util/fs/writeFile/writeFileSync.d.ts +1 -0
  45. package/src/util/fs/writeJsonFile/writeJsonFileSafe.d.ts +1 -0
  46. package/src/{core/util → util/function}/MethodDisabler.d.ts +4 -3
  47. package/src/util/function/funSetName.d.ts +13 -0
  48. package/src/util/node/execInherit.d.ts +1 -0
  49. package/src/{core/util → util/object}/createArrayMerger.d.ts +3 -0
  50. package/src/{core/util → util/object}/createObjectMerger.d.ts +4 -1
  51. package/src/util/object/objUpdatePropertyDescriptors.d.ts +16 -0
  52. package/src/util/object/setNonEnumerable.d.ts +15 -0
  53. package/src/util/os/defaultOpenInEditorCommand.d.ts +5 -0
  54. package/src/util/os/isOSX.d.ts +6 -0
  55. package/src/util/os/isVsCodeInstalled.d.ts +5 -0
  56. package/src/util/os/isWindows.d.ts +8 -0
  57. package/src/util/path/getTempDataPath.d.ts +6 -0
  58. package/src/util/regex/regexEscapeString.d.ts +11 -0
  59. package/src/util/string/strEnsureStartsWith.d.ts +12 -0
  60. package/src/util/string/strFirstCharToUpperCase.d.ts +9 -0
  61. package/src/util/string/strIsLowerCase.d.ts +11 -0
  62. package/src/util/string/strIsUpperCase.d.ts +11 -0
  63. package/src/util/string/strSplitCamelCase.d.ts +11 -0
  64. package/src/util/types/Any.d.ts +1 -0
  65. package/src/util/types/DeepArray.d.ts +10 -0
  66. package/src/util/types/DeepObject.d.ts +14 -0
  67. package/src/util/types/JsonArray.d.ts +6 -0
  68. package/src/util/types/JsonDefinedPrimitive.d.ts +4 -0
  69. package/src/util/types/JsonObject.d.ts +6 -0
  70. package/src/util/types/JsonRawPrimitive.d.ts +5 -0
  71. package/src/util/types/JsonValue.d.ts +7 -0
  72. package/src/util/types/ObjectKey.d.ts +1 -0
  73. package/src/util/types/TConstructor.d.ts +5 -0
  74. package/src/util/types/TFunction.d.ts +4 -0
  75. package/src/util/types/TFunctionNoNew.d.ts +2 -0
  76. package/src/util/types/TPlainObject.d.ts +2 -0
  77. package/src/util/types/TPrimitive.d.ts +1 -0
  78. package/src/util/types/TValidator.d.ts +1 -0
  79. package/src/{validators → util/validation}/createTypedArrayValidator.d.ts +1 -3
  80. package/src/util/validation/ensureThat.d.ts +6 -0
  81. package/src/util/validation/isFunction.d.ts +11 -0
  82. package/src/{validators → util/validation}/isNamedFunction.d.ts +1 -1
  83. package/src/util/validation/isObject.d.ts +15 -0
  84. package/src/util/validation/isPlainObject.d.ts +6 -0
  85. package/src/util/validation/isPrimitive.d.ts +12 -0
  86. package/src/util/validation/numbers/isInteger.d.ts +13 -0
  87. package/src/util/validation/numbers/isValidNumber.d.ts +15 -0
  88. package/src/core/CommandBuilder/Base.d.ts +0 -5
  89. package/src/core/CommandBuilder/CommandBuilder.d.ts +0 -80
  90. package/src/core/CommandBuilder/CommandBuilder.example.d.ts +0 -1
  91. package/src/core/CommandBuilder/CommandBuilderMetaData.d.ts +0 -17
  92. package/src/core/CommandBuilder/CommandFeatureSelector.d.ts +0 -26
  93. package/src/core/CommandBuilder/OutputManager.d.ts +0 -47
  94. package/src/core/CommandBuilder/assertCommandNameNotReserved.d.ts +0 -1
  95. package/src/core/CommandBuilder/ensureBackRefToCommandBuilder.d.ts +0 -7
  96. package/src/core/CommandBuilder/features/action/actionWrapper.d.ts +0 -5
  97. package/src/core/CommandBuilder/features/action/combineVariadicArgs.d.ts +0 -3
  98. package/src/core/CommandBuilder/features/action/debugLogArgsOpts.d.ts +0 -4
  99. package/src/core/CommandBuilder/features/action/deleteOptionsWithDefaultOrNoValue.d.ts +0 -3
  100. package/src/core/CommandBuilder/features/action/getPresetArgsAndOpts.d.ts +0 -3
  101. package/src/core/CommandBuilder/features/action/handleError.d.ts +0 -2
  102. package/src/core/CommandBuilder/features/action/handleOutputOptions.d.ts +0 -2
  103. package/src/core/CommandBuilder/features/action/optsWithGlobalsParsed.d.ts +0 -2
  104. package/src/core/CommandBuilder/features/action/padArgsWithUndefinedUntilExpectedLength.d.ts +0 -3
  105. package/src/core/CommandBuilder/features/action/parseArguments.d.ts +0 -2
  106. package/src/core/CommandBuilder/features/action/parseOptions.d.ts +0 -6
  107. package/src/core/CommandBuilder/features/action/parsedValidArgsOptsWithPresets.d.ts +0 -2
  108. package/src/core/CommandBuilder/features/action/parsedValidArgsWithPresets.d.ts +0 -2
  109. package/src/core/CommandBuilder/features/action/parsedValidOptsWithPresets.d.ts +0 -3
  110. package/src/core/CommandBuilder/features/addConfigCommands.d.ts +0 -3
  111. package/src/core/CommandBuilder/features/addPresetsCommands.d.ts +0 -3
  112. package/src/core/CommandBuilder/features/addUtilCommands.d.ts +0 -2
  113. package/src/core/CommandBuilder/features/assertNoDuplicateCommandNames.d.ts +0 -2
  114. package/src/core/CommandBuilder/features/assertNoDuplicateOptionNames.d.ts +0 -2
  115. package/src/core/CommandBuilder/features/autoAssignMissingOptionFlags.d.ts +0 -14
  116. package/src/core/CommandBuilder/features/autoAssignSubCommandAliases.d.ts +0 -13
  117. package/src/core/CommandBuilder/features/getClosestNonNativeParent.d.ts +0 -2
  118. package/src/core/CommandBuilder/getGlobalOptions.d.ts +0 -3
  119. package/src/core/CommandBuilder/getOwnAndGlobalOptions.d.ts +0 -3
  120. package/src/core/CommandBuilder/initializeCommand.d.ts +0 -2
  121. package/src/core/db/AbstractJsonFileSection.d.ts +0 -45
  122. package/src/core/db/ConfigSection.d.ts +0 -19
  123. package/src/core/db/JsonDB.d.ts +0 -19
  124. package/src/core/db/JsonFile.d.ts +0 -25
  125. package/src/core/db/JsonFileError.d.ts +0 -6
  126. package/src/core/db/PresetsSection.d.ts +0 -14
  127. package/src/core/help/configureHelp.d.ts +0 -2
  128. package/src/core/util/assertPresetArgsOptional.d.ts +0 -3
  129. package/src/core/util/assertValidArguments.d.ts +0 -6
  130. package/src/core/util/assertValidOptions.d.ts +0 -6
  131. package/src/core/util/assertValidPreset.d.ts +0 -3
  132. package/src/core/util/commandExists.d.ts +0 -5
  133. package/src/core/util/commandLocation.d.ts +0 -5
  134. package/src/core/util/errorToString.d.ts +0 -1
  135. package/src/core/util/escapeShellCommandArgument.d.ts +0 -5
  136. package/src/core/util/forEachChildRecursive.d.ts +0 -4
  137. package/src/core/util/getARGV.d.ts +0 -1
  138. package/src/core/util/getAncestors.d.ts +0 -7
  139. package/src/core/util/getChildren.d.ts +0 -4
  140. package/src/core/util/getJsonFilepath.d.ts +0 -2
  141. package/src/core/util/getOptionArgumentName.d.ts +0 -5
  142. package/src/core/util/getRootCommand.d.ts +0 -5
  143. package/src/core/util/getSiblings.d.ts +0 -5
  144. package/src/core/util/hasVariadicArguments.d.ts +0 -5
  145. package/src/core/util/objDestroy.d.ts +0 -2
  146. package/src/core/util/optHasArgument.d.ts +0 -2
  147. package/src/core/util/prefixArray.d.ts +0 -5
  148. package/src/core/util/prefixString.d.ts +0 -5
  149. package/src/core/util/prefixStringsRecursive.d.ts +0 -5
  150. package/src/core/util/renderOptionFlags.d.ts +0 -6
  151. package/src/core/util/setOptionLongName.d.ts +0 -6
  152. package/src/core/util/setOptionShortName.d.ts +0 -6
  153. package/src/core/util/walkAncestors.d.ts +0 -7
  154. package/src/core/util/walkChildren.d.ts +0 -4
  155. package/src/core/util/walkSiblings.d.ts +0 -5
  156. package/src/parsers/createTupleListParser.d.ts +0 -11
  157. package/src/parsers/selector/ArgumentParserSelector.d.ts +0 -7
  158. package/src/parsers/selector/OptionArgumentParserSelector.d.ts +0 -7
  159. package/src/types/IDefinePropertyOptions.d.ts +0 -9
  160. package/src/types/IPresets.d.ts +0 -6
  161. package/src/types/TValidator.d.ts +0 -2
  162. package/src/validators/createLengthValidator.d.ts +0 -7
  163. package/src/validators/isInteger.d.ts +0 -1
  164. package/src/validators/isIntegerArray.d.ts +0 -4
  165. package/src/validators/isNull.d.ts +0 -1
  166. package/src/validators/isNumber.d.ts +0 -1
  167. package/src/validators/isNumberArray.d.ts +0 -4
  168. package/src/validators/isNumericString.d.ts +0 -1
  169. package/src/validators/selector/ArgumentValidatorSelector.d.ts +0 -6
  170. package/src/validators/selector/OptionArgumentValidatorSelector.d.ts +0 -6
  171. package/src/validators/selector/ValidatorSelector.d.ts +0 -17
  172. /package/src/core/{util/splitCombinedArgvShorts.d.ts → splitCombinedArgvShorts.d.ts} +0 -0
  173. /package/src/{core/CommandBuilder → util/errors}/ErrorParser.d.ts +0 -0
  174. /package/src/{core/CommandBuilder/features → util/node}/formatTableForTerminal.d.ts +0 -0
  175. /package/src/{core/util → util/object}/arrAssign.d.ts +0 -0
  176. /package/src/{core/util → util/object}/objAssign.d.ts +0 -0
  177. /package/src/{core/util → util/object}/realizeLazyProperty.d.ts +0 -0
  178. /package/src/{parsers → util/string-parsers}/createBooleanParser.d.ts +0 -0
  179. /package/src/{parsers → util/string-parsers}/createTypedListParser.d.ts +0 -0
  180. /package/src/{parsers → util/string-parsers}/parseBoolean.d.ts +0 -0
  181. /package/src/{parsers → util/string-parsers}/parseInteger.d.ts +0 -0
  182. /package/src/{parsers → util/string-parsers}/parseNumber.d.ts +0 -0
  183. /package/src/{parsers → util/string-parsers}/parseString.d.ts +0 -0
  184. /package/src/{types → util/types}/TStringParser.d.ts +0 -0
  185. /package/src/{validators → util/validation}/isArray.d.ts +0 -0
  186. /package/src/{validators → util/validation}/isBoolean.d.ts +0 -0
  187. /package/src/{validators → util/validation}/isNamedFunctionArray.d.ts +0 -0
  188. /package/src/{validators → util/validation}/isString.d.ts +0 -0
  189. /package/src/{validators → util/validation}/isStringArray.d.ts +0 -0
  190. /package/src/{validators → util/validation}/isStringWithNoSpacesOrDashes.d.ts +0 -0
package/index.esm.js CHANGED
@@ -1,19 +1,13 @@
1
- import { Argument, Help, Command, Option } from 'commander';
2
- import { assertThat, strFirstCharToUpperCase, funSetName, isValidNumber, arrLast, colors, isPlainObject, isPrimitive, regexEscapeString, isFunction, defaultOpenInEditorCommand, strEnsureStartsWith, arrSome, XtError, promptUserEditJsonInTextEditorSync, isObject, readJsonFileSafeSync, funAsyncRateLimit, writeJsonFileSafe } from '@bemoje/util';
1
+ import { Argument, Command, Help, Option, CommanderError, InvalidArgumentError } from 'commander';
2
+ export * from 'commander';
3
+ import colors from 'ansi-colors';
4
+ import fs, { writeFileSync, outputJson, remove } from 'fs-extra';
5
+ export { remove as removeFile, outputFileSync as writeFileSafeSync, writeFileSync, outputJson as writeJsonFileSafe } from 'fs-extra';
6
+ import isAsyncFunction from 'is-async-function';
3
7
  import os from 'os';
4
8
  import path from 'path';
5
9
  import Table from 'cli-table';
6
- import { lookpath } from 'lookpath';
7
-
8
- class Base {
9
- constructor(){
10
- var _Base_nextIndex_this_constructor_name;
11
- this.id = Base.nextIndex[this.constructor.name] = 1 + ((_Base_nextIndex_this_constructor_name = Base.nextIndex[this.constructor.name]) != null ? _Base_nextIndex_this_constructor_name : 0);
12
- }
13
- }
14
- (()=>{
15
- Base.nextIndex = {};
16
- })();
10
+ import { execSync } from 'child_process';
17
11
 
18
12
  /**
19
13
  * Creates a parser function that parses a delimited string into a list of typed values.
@@ -50,7 +44,10 @@ function parseString(string) {
50
44
  return string;
51
45
  }
52
46
 
53
- class AbstractStringParserSelector extends Base {
47
+ class ParserSelector {
48
+ constructor(builder){
49
+ this.builder = builder;
50
+ }
54
51
  string() {
55
52
  return this.custom(parseString);
56
53
  }
@@ -72,20 +69,22 @@ class AbstractStringParserSelector extends Base {
72
69
  delimited(delimiter = ',', parser) {
73
70
  return this.custom(createTypedListParser(delimiter, parser));
74
71
  }
75
- constructor(builder){
76
- super();
77
- this.builder = builder;
78
- }
79
72
  }
80
73
 
81
- class ArgumentParserSelector extends AbstractStringParserSelector {
74
+ class ArgumentParserSelector extends ParserSelector {
75
+ constructor(builder){
76
+ super(builder);
77
+ }
82
78
  custom(parser) {
83
79
  this.builder.cmd.meta.argParsers[this.builder.index] = parser;
84
80
  return this.builder;
85
81
  }
86
82
  }
87
83
 
88
- class ArgumentReader extends Base {
84
+ class ArgumentReader {
85
+ constructor(parent){
86
+ this.parent = parent;
87
+ }
89
88
  get $() {
90
89
  return this.parent.$;
91
90
  }
@@ -113,10 +112,49 @@ class ArgumentReader extends Base {
113
112
  get optional() {
114
113
  return !this.$.required;
115
114
  }
116
- constructor(parent){
117
- super();
118
- this.parent = parent;
119
- }
115
+ }
116
+
117
+ /**
118
+ * Returns the last element of an array.
119
+ * Throws an error if the array is empty.
120
+ * @template T The type of elements in the array.
121
+ * @param array The array to get the last element from.
122
+ * @returns The last element of the array.
123
+ * @throws If the array is empty.
124
+ * @example const numbers = [1, 2, 3, 4, 5];
125
+ * const lastNumber = arrLast(numbers);
126
+ * //=> 5
127
+ */ function arrLast(array) {
128
+ if (!array.length) throw new Error('Cannot get last element of empty array.');
129
+ return array[array.length - 1];
130
+ }
131
+
132
+ function ensureThat(value, validator, options = {}) {
133
+ const result = validator(value, ...options.args ?? []);
134
+ if (result === true) return value;
135
+ const message = typeof result === 'string' ? `${result}. Got: ${value}` : `Expected '${validator.name}'. Got: ${value}`;
136
+ throw new (options.Err ?? Error)(message);
137
+ }
138
+
139
+ /**
140
+ * This function sets the name of a function and returns the function with the new name.
141
+ * @template T - The type of the function.
142
+ * @param name The new name to be set for the function.
143
+ * @param fun The function whose name is to be set.
144
+ * @returns The function with the new name.
145
+ * @example ```ts
146
+ * const myFun = () => 'Hello World';
147
+ * funSetName('newFun', myFun).name;;
148
+ * //=> 'newFun'
149
+ * ```
150
+ */ function funSetName(name, fun) {
151
+ Object.defineProperty(fun, 'name', {
152
+ value: name,
153
+ configurable: true,
154
+ writable: true,
155
+ enumerable: false
156
+ });
157
+ return fun;
120
158
  }
121
159
 
122
160
  /**
@@ -129,6 +167,17 @@ function isNamedFunctionArray(array) {
129
167
  return Array.isArray(array) && array.every(isNamedFunction);
130
168
  }
131
169
 
170
+ /**
171
+ * Converts the first character of a string to uppercase.
172
+ * @param string The string to be converted.
173
+ * @example ```ts
174
+ * strFirstCharToUpperCase('hello');
175
+ * //=> 'Hello'
176
+ * ```
177
+ */ function strFirstCharToUpperCase(string) {
178
+ return string.charAt(0).toUpperCase() + string.substring(1);
179
+ }
180
+
132
181
  /**
133
182
  * Creates a validator function that checks whether the input is an array where all elements are valid according to every validator provided.
134
183
  *
@@ -138,30 +187,57 @@ function isNamedFunctionArray(array) {
138
187
  * @throws TypeError - if no name is provided and not all validators are named functions.
139
188
  */ function createTypedArrayValidator(validators, name) {
140
189
  if (!name) {
141
- assertThat(validators, isNamedFunctionArray);
142
- name = 'isArrayWhereEachIs' + validators.map((fun)=>strFirstCharToUpperCase(fun.name)).join('And');
190
+ ensureThat(validators, isNamedFunctionArray);
191
+ name = 'isArrayWhereEach' + validators.map((fun)=>strFirstCharToUpperCase(fun.name)).join('And');
143
192
  }
144
193
  return funSetName(name, function(array) {
145
194
  return Array.isArray(array) && array.every((value)=>validators.every((isValid)=>isValid(value)));
146
195
  });
147
196
  }
148
197
 
149
- const isInteger = Number.isInteger;
150
-
151
- function isNumber(value) {
152
- return isValidNumber(value);
153
- }
198
+ /**
199
+ * Checks if the provided number is an integer.
200
+ * @remarks This function uses the built-in `Number.isInteger` method.
201
+ * @param int The number to check.
202
+ * @returns A boolean indicating whether the provided number is an integer.
203
+ * @example ```ts
204
+ * isInteger(5);
205
+ * //=> true
206
+ * isInteger(5.5);
207
+ * //=> false
208
+ * ```
209
+ */ const isInteger = Number.isInteger;
154
210
 
155
211
  function isString(value) {
156
212
  return typeof value === 'string';
157
213
  }
158
214
 
159
- class AbstractValidatorSelector extends Base {
215
+ /**
216
+ * Checks if the provided value is a valid number.
217
+ * @remarks This function checks if the provided value is a finite number and not NaN.
218
+ * @param number The value to check.
219
+ * @returns A boolean indicating whether the provided value is a valid number.
220
+ * @example ```ts
221
+ * isValidNumber(123);
222
+ * //=> true
223
+ * isValidNumber(NaN);
224
+ * //=> false
225
+ * isValidNumber(Infinity);
226
+ * //=> false
227
+ * ```
228
+ */ function isValidNumber(number) {
229
+ return isFinite(number) && !isNaN(number);
230
+ }
231
+
232
+ class ValidatorSelector {
233
+ constructor(builder){
234
+ this.builder = builder;
235
+ }
160
236
  isString() {
161
237
  return this.custom(isString);
162
238
  }
163
239
  isNumber() {
164
- return this.custom(isNumber);
240
+ return this.custom(isValidNumber);
165
241
  }
166
242
  isInteger() {
167
243
  return this.custom(isInteger);
@@ -170,7 +246,7 @@ class AbstractValidatorSelector extends Base {
170
246
  return this.arrayWhereEach(isString);
171
247
  }
172
248
  isNumberArray() {
173
- return this.arrayWhereEach(isNumber);
249
+ return this.arrayWhereEach(isValidNumber);
174
250
  }
175
251
  isIntegerArray() {
176
252
  return this.arrayWhereEach(isInteger);
@@ -178,13 +254,12 @@ class AbstractValidatorSelector extends Base {
178
254
  arrayWhereEach(...validators) {
179
255
  return this.custom(createTypedArrayValidator(validators));
180
256
  }
181
- constructor(builder){
182
- super();
183
- this.builder = builder;
184
- }
185
257
  }
186
258
 
187
- class ArgumentValidatorSelector extends AbstractValidatorSelector {
259
+ class ArgumentValidatorSelector extends ValidatorSelector {
260
+ constructor(builder){
261
+ super(builder);
262
+ }
188
263
  custom(validator) {
189
264
  arrLast(this.builder.cmd.meta.argValidators).push(validator);
190
265
  return this.builder;
@@ -202,16 +277,19 @@ function realizeLazyProperty(obj, key, value) {
202
277
  }
203
278
 
204
279
  /**
205
- * Wrapper around the @see Argument class, for more intuitive construction.
206
- */ class ArgumentBuilder extends Base {
280
+ * Wrapper around the @see Argument class from 'commander'.
281
+ */ class ArgumentBuilder {
282
+ constructor(cmd, name){
283
+ this.cmd = cmd;
284
+ this.$ = new Argument(name);
285
+ this.index = cmd.meta.argValidators.length;
286
+ cmd.meta.argValidators[this.index] = [];
287
+ }
207
288
  description(string) {
208
289
  this.$.description = string;
209
290
  return this;
210
291
  }
211
292
  default(value, description) {
212
- if (this.$.required) {
213
- throw new Error('Cannot set default value on required argument: ' + this.$.name());
214
- }
215
293
  this.$.default(value, description);
216
294
  return this;
217
295
  }
@@ -228,209 +306,87 @@ function realizeLazyProperty(obj, key, value) {
228
306
  get get() {
229
307
  return realizeLazyProperty(this, 'get', new ArgumentReader(this));
230
308
  }
231
- constructor(cmd, name){
232
- super();
233
- this.cmd = cmd;
234
- this.$ = new Argument(name);
235
- this.index = cmd.meta.argValidators.length;
236
- cmd.meta.argValidators[this.index] = [];
237
- }
238
309
  }
239
310
 
240
- function assertCommandNameNotReserved(name) {
241
- if (name === 'u' || name === 'util') {
242
- throw new Error(`Name '${name}' is reserved and is not available as name or alias.`);
243
- }
244
- }
245
-
246
- function _extends() {
247
- _extends = Object.assign || function assign(target) {
248
- for(var i = 1; i < arguments.length; i++){
249
- var source = arguments[i];
250
- for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
311
+ /**
312
+ * Creates a function that merges arrays based on a predicate function.
313
+ */ function createArrayMerger(predicate) {
314
+ return function arrMerge(target, ...sources) {
315
+ for (const src of sources){
316
+ for(let i = 0; i < src.length; i++){
317
+ if (predicate(src[i], i, src)) {
318
+ target[i] = src[i];
319
+ }
320
+ }
251
321
  }
252
322
  return target;
253
323
  };
254
- return _extends.apply(this, arguments);
255
324
  }
256
325
 
257
- class CommandBuilderMetaData extends Base {
258
- get argParsers() {
259
- return realizeLazyProperty(this, 'argParsers', []);
260
- }
261
- get argValidators() {
262
- return realizeLazyProperty(this, 'argValidators', []);
263
- }
264
- get optParsers() {
265
- return realizeLazyProperty(this, 'optParsers', {});
266
- }
267
- get optValidators() {
268
- return realizeLazyProperty(this, 'optValidators', {});
269
- }
270
- constructor(...args){
271
- super(...args);
272
- this.subcommands = [];
273
- this.globalOptions = [];
274
- this.hiddenGlobalOptions = new Set();
275
- this.isNative = false;
276
- }
277
- }
326
+ const arrAssign = createArrayMerger((value)=>value != null);
278
327
 
279
328
  /**
280
- *
281
- */ class CommandFeatureSelector extends Base {
282
- debugToggle(info) {
283
- this.cmd.outputDebugInfo('featureEnabled', ()=>info);
284
- }
285
- utils(boolean = true) {
286
- if (this._utils === boolean) return this;
287
- if (this.cmd.meta.isNative) throw new Error('Cannot configure utils for native command.');
288
- this._utils = boolean;
289
- this.debugToggle({
290
- utils: boolean
291
- });
292
- return this;
293
- }
294
- config(boolean = true) {
295
- if (this._config === boolean) return this;
296
- if (this.cmd.meta.isNative) throw new Error('Cannot configure config for native command.');
297
- if (this._config !== boolean) {
298
- this._config = boolean;
299
- this.debugToggle({
300
- config: boolean
301
- });
302
- }
303
- return this;
304
- }
305
- presets(boolean = true) {
306
- if (this.cmd.meta.isNative) {
307
- throw new Error('Cannot configure presets for native command.');
308
- }
309
- if (this._presets !== boolean) {
310
- this._presets = boolean;
311
- this.debugToggle({
312
- presets: boolean
313
- });
314
- }
315
- return this;
316
- }
317
- autoAssignMissingOptionFlags(boolean = true) {
318
- if (this._autoAssignMissingOptionFlags !== boolean) {
319
- this._autoAssignMissingOptionFlags = boolean;
320
- this.debugToggle({
321
- autoAssignMissingOptionFlags: boolean
322
- });
323
- }
324
- return this;
325
- }
326
- autoAssignSubCommandAliases(boolean = true) {
327
- if (this._autoAssignSubCommandAliases !== boolean) {
328
- this._autoAssignSubCommandAliases = boolean;
329
- this.debugToggle({
330
- autoAssignSubCommandAliases: boolean
331
- });
332
- }
333
- return this;
334
- }
335
- all(boolean = true) {
336
- this.utils(boolean);
337
- this.config(boolean);
338
- this.presets(boolean);
339
- this.autoAssignMissingOptionFlags(boolean);
340
- this.autoAssignSubCommandAliases(boolean);
341
- return this.cmd;
342
- }
343
- get isUtilsEnabled() {
344
- if (this.cmd.meta.isNative) return false;
345
- return this._utils;
346
- }
347
- get isConfigEnabled() {
348
- if (this.cmd.meta.isNative) return false;
349
- return this._config;
350
- }
351
- get isPresetsEnabled() {
352
- if (this.cmd.meta.isNative) return false;
353
- return this._presets;
354
- }
355
- get isAutoAssignMissingOptionFlagsEnabled() {
356
- return this._autoAssignMissingOptionFlags;
357
- }
358
- get isAutoAssignSubCommandAliasesEnabled() {
359
- return this._autoAssignSubCommandAliases;
360
- }
361
- constructor(cmd){
362
- var _cmd_parent, _cmd;
363
- super();
364
- this.cmd = cmd;
365
- this._utils = false;
366
- this._config = false;
367
- this._presets = false;
368
- this._autoAssignMissingOptionFlags = false;
369
- this._autoAssignSubCommandAliases = false;
370
- const parent = (_cmd = cmd) == null ? void 0 : (_cmd_parent = _cmd.parent) == null ? void 0 : _cmd_parent.features;
371
- if (parent) {
372
- if (!this.cmd.meta.isNative) {
373
- this._utils = parent._utils;
374
- this._config = parent._config;
375
- this._presets = parent._presets;
376
- }
377
- this._autoAssignMissingOptionFlags = parent._autoAssignMissingOptionFlags;
378
- this._autoAssignSubCommandAliases = parent._autoAssignSubCommandAliases;
329
+ * Checks if at least one element in the array satisfies the provided predicate.
330
+ * @param predicate The predicate function to apply to each element.
331
+ * @template T The type of elements in the input array.
332
+ * @returns Returns `true` if at least one element in the array passes the test implemented by the provided function, otherwise `false`.
333
+ * @param input The array to check.
334
+ * @example ```ts
335
+ * const numbers = [1, 2, 3, 4, 5];
336
+ * const isEven = (num) => num % 2 === 0;
337
+ * arrSome(numbers, isEven);
338
+ * //=> true
339
+ * ```
340
+ */ function arrSome(input, predicate) {
341
+ for(let i = 0, len = input.length; i < len; i++){
342
+ if (predicate(input[i], i, input) === true) {
343
+ return true;
379
344
  }
380
345
  }
346
+ return false;
381
347
  }
382
348
 
383
349
  /**
384
- * Returns an iterator that walks the command's ancestors, optionally starting with the command itself.
385
- */ function* walkAncestors(cmd, options) {
386
- var _options;
387
- if ((_options = options) == null ? void 0 : _options.includeSelf) yield cmd;
388
- let node = cmd.parent;
389
- while(node){
390
- yield node;
391
- node = node.parent;
392
- }
350
+ * Checks if the provided value is a plain object, i.e. an object created by the native base `Object` constructor.
351
+ */ function isPlainObject(value) {
352
+ if (Object.prototype.toString.call(value) !== '[object Object]') return false;
353
+ if (!value?.constructor) return true;
354
+ if (Object.prototype.toString.call(value.constructor.prototype) !== '[object Object]') return false;
355
+ if (!Object.prototype.hasOwnProperty.call(value.constructor.prototype, 'isPrototypeOf')) return false;
356
+ return true;
393
357
  }
394
358
 
395
359
  /**
396
- * Get a command's ancestors, optionally starting from the command itself.
397
- */ function getAncestors(cmd, options) {
398
- return [
399
- ...walkAncestors(cmd, options)
400
- ];
401
- }
402
-
403
- function splitCombinedArgvShorts(argv) {
404
- return argv.map((arg)=>{
405
- if (arg.length < 3 || !arg.startsWith('-') || arg.startsWith('--') || arg.includes('=')) {
406
- return arg;
407
- }
408
- return Array.from(arg.replace('-', '')).map((s)=>'-' + s);
409
- }).flat();
410
- }
411
-
412
- function getARGV(argv) {
413
- if (argv) ARGV = splitCombinedArgvShorts(argv);
414
- return ARGV.slice();
360
+ * Checks if the provided value is a primitive type (null, undefined, bigint, boolean, number, string or symbol).
361
+ * @param value The value to check.
362
+ * @returns A boolean indicating whether the provided value is a primitive type.
363
+ * @example
364
+ * isPrimitive(123);
365
+ * //=> true
366
+ * isPrimitive({});
367
+ * //=> false
368
+ */ function isPrimitive(value) {
369
+ return typeof value !== 'object' && typeof value !== 'function' || value === null;
415
370
  }
416
- let ARGV = splitCombinedArgvShorts(process.argv.slice(2));
417
371
 
418
- function getGlobalOptions(cmd) {
419
- const result = [];
420
- const ancestors = getAncestors(cmd, {
421
- includeSelf: true
422
- }).reverse();
423
- for (const anc of ancestors){
424
- for (const gopt of anc.meta.globalOptions){
425
- if (!cmd.meta.hiddenGlobalOptions.has(gopt)) {
426
- result.push(gopt);
427
- }
428
- }
429
- }
430
- return result;
372
+ /**
373
+ * Escapes special characters in a string to be used in a regular expression.
374
+ * @param str The input string to escape.
375
+ * @returns The escaped string.
376
+ * @example ```ts
377
+ * const input = 'Hello, world!';
378
+ * regexEscapeString(input);;
379
+ * //=> 'Hello, world!'
380
+ * ```
381
+ */ function regexEscapeString(str) {
382
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
383
+ ;
431
384
  }
432
385
 
433
386
  class ErrorParser {
387
+ constructor(error){
388
+ this.error = error instanceof Error ? error : new Error(String(error));
389
+ }
434
390
  get name() {
435
391
  return this.error.name;
436
392
  }
@@ -455,12 +411,9 @@ class ErrorParser {
455
411
  toJSON() {
456
412
  return this.toObject();
457
413
  }
458
- constructor(error){
459
- this.error = error instanceof Error ? error : new Error(String(error));
460
- }
461
414
  }
462
415
  function errPrettyStack(error, parsedStackFrames) {
463
- const frames = parsedStackFrames != null ? parsedStackFrames : errParseStack(error.stack || '');
416
+ const frames = parsedStackFrames ?? errParseStack(error.stack || '');
464
417
  // width of the first column = the longest frame.cell string
465
418
  const offset = 2 + frames.reduce((acc, frame)=>Math.max(acc, frame[0].length), 0);
466
419
  // type and message
@@ -515,7 +468,6 @@ function errPrettyStack(error, parsedStackFrames) {
515
468
  } else if (isPrimitive(error[key])) {
516
469
  s += String(error[key]);
517
470
  } else if (error[key] != null && typeof error[key] === 'object') {
518
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
519
471
  s += error[key].toString();
520
472
  } else {
521
473
  s += JSON.stringify(error[key]);
@@ -572,19 +524,18 @@ function errToObject(error) {
572
524
  // console.log('----------------------')
573
525
  // }
574
526
 
575
- function _class_private_field_loose_base(receiver, privateKey) {
576
- if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
577
- throw new TypeError("attempted to use private field on non-instance");
578
- }
579
- return receiver;
580
- }
581
-
582
- var id = 0;
583
- function _class_private_field_loose_key(name) {
584
- return "__private_" + id++ + "_" + name;
527
+ /**
528
+ * Checks if the given value is a function.
529
+ *
530
+ * @example ```ts
531
+ * isFunction(class {}); //=> true
532
+ * isFunction(function () {}); //=> true
533
+ * isFunction(() => {}); //=> true
534
+ * ```
535
+ */ function isFunction(value) {
536
+ return typeof value === 'function' && value !== Function.prototype;
585
537
  }
586
538
 
587
- var _defaultDescriptor = /*#__PURE__*/ _class_private_field_loose_key("_defaultDescriptor"), _noop = /*#__PURE__*/ _class_private_field_loose_key("_noop"), _memoized = /*#__PURE__*/ _class_private_field_loose_key("_memoized"), _obj = /*#__PURE__*/ _class_private_field_loose_key("_obj"), _key = /*#__PURE__*/ _class_private_field_loose_key("_key"), _original = /*#__PURE__*/ _class_private_field_loose_key("_original"), _hasOwn = /*#__PURE__*/ _class_private_field_loose_key("_hasOwn"), _originalDescriptor = /*#__PURE__*/ _class_private_field_loose_key("_originalDescriptor"), _noopDescriptor = /*#__PURE__*/ _class_private_field_loose_key("_noopDescriptor"), _isEnabled = /*#__PURE__*/ _class_private_field_loose_key("_isEnabled");
588
539
  /**
589
540
  * A class that creates an object with methods for disabling/enabling a given method on a given object.
590
541
  *
@@ -600,503 +551,438 @@ var _defaultDescriptor = /*#__PURE__*/ _class_private_field_loose_key("_defaultD
600
551
  * console.log('This will print')
601
552
  *
602
553
  * assert(md.original === process.stdout.write)
603
- */ class MethodDisabler extends Base {
554
+ */ class MethodDisabler {
555
+ static #defaultDescriptor(value) {
556
+ return {
557
+ value,
558
+ writable: true,
559
+ enumerable: true,
560
+ configurable: true
561
+ };
562
+ }
563
+ static #noop = (...args)=>void 0;
564
+ static #memoized = new WeakMap();
565
+ #obj;
566
+ #key;
567
+ #original;
568
+ #hasOwn;
569
+ #originalDescriptor;
570
+ #noopDescriptor;
571
+ #isEnabled;
572
+ /**
573
+ * @param obj - The object on which the method is defined.
574
+ * @param key - The property name of the method.
575
+ */ constructor(obj, key){
576
+ this.#isEnabled = true;
577
+ ensureThat(obj[key], isFunction);
578
+ this.#obj = obj;
579
+ this.#key = key;
580
+ this.#original = obj[key];
581
+ this.#hasOwn = Object.hasOwn(obj, key);
582
+ this.#originalDescriptor = this.#hasOwn ? Object.getOwnPropertyDescriptor(obj, key) : MethodDisabler.#defaultDescriptor(this.#original);
583
+ this.#noopDescriptor = Object.assign({}, this.#originalDescriptor, {
584
+ value: MethodDisabler.#noop
585
+ });
586
+ // return stored if memoized
587
+ if (MethodDisabler.#memoized.get(obj)?.has(key)) {
588
+ return MethodDisabler.#memoized.get(obj)?.get(key);
589
+ }
590
+ //memoize
591
+ if (!MethodDisabler.#memoized.has(obj)) MethodDisabler.#memoized.set(obj, new Map());
592
+ const methods = MethodDisabler.#memoized.get(obj);
593
+ methods.set(key, this);
594
+ }
604
595
  /**
605
596
  * Disable the method.
606
597
  */ disable() {
607
598
  if (!this.isEnabled) return;
608
- Object.defineProperty(_class_private_field_loose_base(this, _obj)[_obj], _class_private_field_loose_base(this, _key)[_key], _class_private_field_loose_base(this, _noopDescriptor)[_noopDescriptor]);
609
- _class_private_field_loose_base(this, _isEnabled)[_isEnabled] = false;
599
+ Object.defineProperty(this.#obj, this.#key, this.#noopDescriptor);
600
+ this.#isEnabled = false;
610
601
  }
611
602
  /**
612
603
  * Enable the method.
613
604
  */ enable() {
614
605
  if (this.isEnabled) return;
615
- if (_class_private_field_loose_base(this, _hasOwn)[_hasOwn]) Object.defineProperty(_class_private_field_loose_base(this, _obj)[_obj], _class_private_field_loose_base(this, _key)[_key], _class_private_field_loose_base(this, _originalDescriptor)[_originalDescriptor]);
616
- else delete _class_private_field_loose_base(this, _obj)[_obj][_class_private_field_loose_base(this, _key)[_key]];
617
- _class_private_field_loose_base(this, _isEnabled)[_isEnabled] = true;
606
+ if (this.#hasOwn) Object.defineProperty(this.#obj, this.#key, this.#originalDescriptor);
607
+ else delete this.#obj[this.#key];
608
+ this.#isEnabled = true;
618
609
  }
619
610
  /**
620
611
  * The original method before it was disabled.
621
612
  */ get original() {
622
- return _class_private_field_loose_base(this, _original)[_original];
613
+ return this.#original;
623
614
  }
624
615
  /**
625
616
  * Whether the method is currently enabled.
626
617
  */ get isEnabled() {
627
- return _class_private_field_loose_base(this, _isEnabled)[_isEnabled];
628
- }
629
- /**
630
- * @param obj - The object on which the method is defined.
631
- * @param key - The property name of the method.
632
- */ constructor(obj, key){
633
- var _class_private_field_loose_base__memoized_get;
634
- super();
635
- Object.defineProperty(this, _obj, {
636
- writable: true,
637
- value: void 0
638
- });
639
- Object.defineProperty(this, _key, {
640
- writable: true,
641
- value: void 0
642
- });
643
- Object.defineProperty(this, _original, {
644
- writable: true,
645
- value: void 0
646
- });
647
- Object.defineProperty(this, _hasOwn, {
648
- writable: true,
649
- value: void 0
650
- });
651
- Object.defineProperty(this, _originalDescriptor, {
652
- writable: true,
653
- value: void 0
654
- });
655
- Object.defineProperty(this, _noopDescriptor, {
656
- writable: true,
657
- value: void 0
658
- });
659
- Object.defineProperty(this, _isEnabled, {
660
- writable: true,
661
- value: void 0
662
- });
663
- _class_private_field_loose_base(this, _isEnabled)[_isEnabled] = true;
664
- assertThat(obj[key], isFunction);
665
- _class_private_field_loose_base(this, _obj)[_obj] = obj;
666
- _class_private_field_loose_base(this, _key)[_key] = key;
667
- _class_private_field_loose_base(this, _original)[_original] = obj[key];
668
- _class_private_field_loose_base(this, _hasOwn)[_hasOwn] = Object.hasOwn(obj, key);
669
- _class_private_field_loose_base(this, _originalDescriptor)[_originalDescriptor] = _class_private_field_loose_base(this, _hasOwn)[_hasOwn] ? Object.getOwnPropertyDescriptor(obj, key) : _class_private_field_loose_base(MethodDisabler, _defaultDescriptor)[_defaultDescriptor](_class_private_field_loose_base(this, _original)[_original]);
670
- _class_private_field_loose_base(this, _noopDescriptor)[_noopDescriptor] = Object.assign({}, _class_private_field_loose_base(this, _originalDescriptor)[_originalDescriptor], {
671
- value: _class_private_field_loose_base(MethodDisabler, _noop)[_noop]
672
- });
673
- // return stored if memoized
674
- if ((_class_private_field_loose_base__memoized_get = _class_private_field_loose_base(MethodDisabler, _memoized)[_memoized].get(obj)) == null ? void 0 : _class_private_field_loose_base__memoized_get.has(key)) {
675
- var _class_private_field_loose_base__memoized_get1;
676
- return (_class_private_field_loose_base__memoized_get1 = _class_private_field_loose_base(MethodDisabler, _memoized)[_memoized].get(obj)) == null ? void 0 : _class_private_field_loose_base__memoized_get1.get(key);
677
- }
678
- //memoize
679
- if (!_class_private_field_loose_base(MethodDisabler, _memoized)[_memoized].has(obj)) _class_private_field_loose_base(MethodDisabler, _memoized)[_memoized].set(obj, new Map());
680
- const methods = _class_private_field_loose_base(MethodDisabler, _memoized)[_memoized].get(obj);
681
- methods.set(key, this);
618
+ return this.#isEnabled;
682
619
  }
683
620
  }
684
- Object.defineProperty(MethodDisabler, _defaultDescriptor, {
685
- value: defaultDescriptor
686
- });
687
- Object.defineProperty(MethodDisabler, _noop, {
688
- writable: true,
689
- value: (...args)=>void 0
690
- });
691
- Object.defineProperty(MethodDisabler, _memoized, {
692
- writable: true,
693
- value: new WeakMap()
694
- });
695
- function defaultDescriptor(value) {
696
- return {
697
- value,
698
- writable: true,
699
- enumerable: true,
700
- configurable: true
701
- };
702
- }
703
621
 
704
- const stdout = new MethodDisabler(process.stdout, 'write');
705
- const stderr = new MethodDisabler(process.stderr, 'write');
706
- const debug = new MethodDisabler(console, 'debug');
707
- debug.disable();
708
622
  /**
709
- *
623
+ * The OutputManager class manages the output of debug messages to the console.
710
624
  */ class OutputManager {
711
- static getInstance() {
712
- if (!this.instance) {
713
- this.instance = new OutputManager();
714
- }
625
+ /**
626
+ * Returns the singleton instance of the OutputManager class.
627
+ * If the instance does not exist, it creates a new one.
628
+ */ static getInstance() {
629
+ if (!this.instance) this.instance = new OutputManager();
715
630
  return this.instance;
716
631
  }
717
- reset() {
718
- this.colors.enable();
632
+ /**
633
+ * Constructs a new instance of the OutputManager class.
634
+ * console.debug is disabled by default.
635
+ */ constructor(){
636
+ /**
637
+ * Ansi-colors object
638
+ */ this.colors = colors;
639
+ /**
640
+ * A MethodDisabler instance for disabling the write method of the process.stdout object.
641
+ */ this.stdout = new MethodDisabler(process.stdout, 'write');
642
+ /**
643
+ * A MethodDisabler instance for disabling the write method of the process.stderr object.
644
+ */ this.stderr = new MethodDisabler(process.stderr, 'write');
645
+ /**
646
+ * A MethodDisabler instance for disabling the debug method of the console object.
647
+ */ this.debug = new MethodDisabler(console, 'debug');
648
+ /**
649
+ * An array that holds queued debug messages.
650
+ */ this.debugMsgQueue = [];
651
+ this.debug.disable();
652
+ colors.enabled = (()=>{
653
+ const { FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM } = process.env;
654
+ return !NODE_DISABLE_COLORS && NO_COLOR == null && TERM !== 'dumb' && (FORCE_COLOR != null && FORCE_COLOR !== '0' || process.stdout && process.stdout.isTTY);
655
+ })();
656
+ }
657
+ get queueSize() {
658
+ return this.debugMsgQueue.length;
659
+ }
660
+ /**
661
+ * Resets the OutputManager to its default state.
662
+ * - Enables colors.
663
+ * - Enables the write method of the process.stdout object.
664
+ * - Enables the write method of the process.stderr object.
665
+ * - Disables the debug method of the console object.
666
+ * @returns The OutputManager instance.
667
+ */ reset() {
668
+ this.colors.enabled = true;
719
669
  this.stdout.enable();
720
670
  this.stderr.enable();
721
671
  this.debug.disable();
722
672
  return this;
723
673
  }
724
- outputDebug(fn) {
674
+ /**
675
+ * Outputs a debug message to the console.
676
+ * If the debug method is enabled, the message is immediately logged to the console.
677
+ * Otherwise, the message is added to the debug message queue.
678
+ * @param fn - A function that returns the debug message.
679
+ */ outputDebug(fn) {
725
680
  if (this.debug.isEnabled) console.debug(fn());
726
- else this.debugMsgs.push(fn);
727
- }
728
- outputDebugMessages() {
729
- this.debugMsgs.forEach((fn)=>console.debug(fn()));
730
- this.debugMsgs = [];
681
+ else this.debugMsgQueue.push(fn);
731
682
  }
732
- constructor(){
733
- this.colors = colors;
734
- this.stdout = stdout;
735
- this.stderr = stderr;
736
- this.debug = debug;
737
- this.debugMsgs = [];
683
+ /**
684
+ * Drains the debug message queue by logging all the messages to the console.
685
+ */ drainDebugMessageQueue() {
686
+ this.debugMsgQueue.forEach((fn)=>console.debug(fn()));
687
+ this.debugMsgQueue.splice(0, this.debugMsgQueue.length);
738
688
  }
739
689
  }
740
690
 
741
- function handleError(cmd, error) {
742
- const parsed = new ErrorParser(error);
743
- if (OutputManager.getInstance().debug.isEnabled) {
744
- console.error(parsed.prettyStack());
745
- } else {
746
- cmd.outputUserError(parsed.summary());
747
- }
748
- }
749
-
750
- function handleOutputOptions(cmd) {
751
- const opts = cmd.$.optsWithGlobals();
752
- const om = OutputManager.getInstance().reset();
753
- if (opts['disableColor']) om.colors.disable();
754
- if (opts['disableStderr']) om.stderr.disable();
755
- if (opts['disableStdout']) om.stdout.disable();
756
- if (opts['debug']) {
757
- om.debug.enable();
758
- om.outputDebugMessages();
759
- }
760
- }
761
-
762
- function debugLogArgsOpts(cb, args, opts, presetArgs, presetOpts, presetOrder) {
763
- if (opts['debug']) {
764
- cb.outputDebugInfo('action', ()=>{
765
- return _extends({}, cb.features.isPresetsEnabled ? {
766
- presetOrder,
767
- presetArgs,
768
- presetOpts
769
- } : {}, {
770
- args,
771
- opts,
772
- command: [
773
- cb.root.name,
774
- ...getARGV()
775
- ].join(' ')
776
- });
777
- });
691
+ class CommandBuilderMetaData {
692
+ get actionHandler() {
693
+ return async function defaultActionHandler() {
694
+ this.outputHelp();
695
+ };
778
696
  }
779
- }
780
-
781
- function getPresetArgsAndOpts(cmd) {
782
- if (!cmd.features.isPresetsEnabled) return [
783
- [],
784
- [],
785
- []
786
- ];
787
- const presets = cmd.db.presets.getAll();
788
- const opts = cmd.$.optsWithGlobals();
789
- const selectedPresets = Object.keys(presets).filter((name)=>opts[name] === true);
790
- const order = new Set();
791
- for (const name of [
792
- 'defaults',
793
- ...selectedPresets
794
- ]){
795
- for (const n of presets[name].presets.concat(name)){
796
- if (order.has(n)) order.delete(n);
797
- order.add(n);
798
- }
697
+ get errorHandler() {
698
+ return function defaultErrorHandler(error) {
699
+ const parsed = new ErrorParser(error);
700
+ if (OutputManager.getInstance().debug.isEnabled) {
701
+ console.error(parsed.prettyStack());
702
+ this.outputError(parsed.summary());
703
+ } else {
704
+ this.outputError(parsed.summary());
705
+ }
706
+ };
707
+ }
708
+ get hasCustomActionHandler() {
709
+ return Object.hasOwn(this, 'actionHandler');
710
+ }
711
+ get hasCustomErrorHandler() {
712
+ return Object.hasOwn(this, 'errorHandler');
713
+ }
714
+ constructor(){
715
+ this.subcommands = [];
716
+ this.globalOptions = [];
717
+ this.hiddenGlobalOptions = new Set();
718
+ this.presetOptionKeys = [];
719
+ this.argParsers = [];
720
+ this.argValidators = [];
721
+ this.optParsers = {};
722
+ this.optValidators = {};
723
+ this.rawArgs = [];
724
+ this.isNative = false;
725
+ this.isInitialized = false;
799
726
  }
800
- const presetOrder = [
801
- ...order
802
- ];
803
- const presetArgs = presetOrder.map((name)=>presets[name].args);
804
- const presetOpts = presetOrder.map((name)=>presets[name].options);
805
- return [
806
- presetArgs,
807
- presetOpts,
808
- presetOrder
809
- ];
810
727
  }
811
728
 
812
- function createArrayMerger(predicate) {
813
- return function arrMerge(target, ...sources) {
814
- for (const src of sources){
815
- for(let i = 0; i < src.length; i++){
816
- if (predicate(src[i], i, src)) {
817
- target[i] = src[i];
818
- }
819
- }
729
+ function splitCombinedArgvShorts(argv) {
730
+ return argv.map((arg)=>{
731
+ if (arg.length < 3 || !arg.startsWith('-') || arg.startsWith('--') || arg.includes('=')) {
732
+ return arg;
820
733
  }
821
- return target;
822
- };
734
+ return Array.from(arg.replace('-', '')).map((s)=>'-' + s);
735
+ }).flat();
823
736
  }
824
737
 
825
- const arrAssign = createArrayMerger((value)=>value != null);
826
-
827
- /**
828
- * Validate ALREADY PARSED args using the validators defined in the command builder.
829
- */ function assertValidArguments(cmd, parsedArgs) {
830
- const last = cmd.arguments.length - 1;
831
- parsedArgs.forEach((arg, i)=>{
832
- if (arg == null) return;
833
- const index = i > last ? last : i;
834
- const validators = cmd.meta.argValidators[index];
835
- if (!validators) return;
836
- for (const isValid of validators){
837
- assertThat(arg, isValid);
838
- }
839
- });
840
- return parsedArgs;
841
- }
738
+ const commanderBackRefs = new WeakMap();
739
+ const oldParse = Command.prototype.parse;
740
+ Command.prototype.parse = function parse(argv, options) {
741
+ if (argv) {
742
+ argv = splitCombinedArgvShorts(argv.slice());
743
+ this.builder.meta.rawArgs = argv.slice(options?.from === 'user' ? 0 : 2);
744
+ } else {
745
+ this.builder.meta.rawArgs = process.argv.slice(2);
746
+ }
747
+ return oldParse.call(this, argv, options);
748
+ };
749
+ const oldParseAsync = Command.prototype.parseAsync;
750
+ Command.prototype.parseAsync = async function(argv, options) {
751
+ if (argv) {
752
+ argv = splitCombinedArgvShorts(argv.slice());
753
+ this.builder.meta.rawArgs = argv.slice(options?.from === 'user' ? 0 : 2);
754
+ } else {
755
+ this.builder.meta.rawArgs = process.argv.slice(2);
756
+ }
757
+ return await oldParseAsync.call(this, argv, options);
758
+ };
759
+ Object.defineProperty(Command.prototype, 'builder', {
760
+ get () {
761
+ const ins = commanderBackRefs.get(this);
762
+ if (!ins) throw new Error(`CommandBuilder not found for command ${this.name()}`);
763
+ return ins;
764
+ }
765
+ });
842
766
 
843
767
  /**
844
- * Returns whether a command's last argument is variadic.
845
- */ function hasVariadicArguments(cmd) {
846
- if (!cmd.arguments.length) return false;
847
- return arrLast(cmd.arguments).variadic;
848
- }
849
-
850
- function combineVariadicArgs(cmd, result) {
851
- if (hasVariadicArguments(cmd) && result.length && !Array.isArray(arrLast(result))) {
852
- const rest = result.splice(cmd.arguments.length - 1);
853
- result.push(rest.filter((arg)=>arg != null));
768
+ * Represents a selector for enabling or disabling command features.
769
+ */ class CommandFeatureSelector {
770
+ /**
771
+ * Creates a new instance of the CommandFeatureSelector class.
772
+ * @param cmd - The command builder instance.
773
+ */ constructor(cmd){
774
+ this.cmd = cmd;
775
+ this./**
776
+ * Indicates whether the app data feature is enabled.
777
+ */ isAppDataEnabled = false;
778
+ this./**
779
+ * Indicates whether the config feature is enabled.
780
+ */ isConfigEnabled = false;
781
+ this./**
782
+ * Indicates whether the presets feature is enabled.
783
+ */ isPresetsEnabled = false;
784
+ this./**
785
+ * Indicates whether the auto assign missing option flags feature is enabled.
786
+ */ isAutoAssignMissingOptionFlagsEnabled = false;
787
+ this./**
788
+ * Indicates whether the auto assign sub command aliases feature is enabled.
789
+ */ isAutoAssignSubCommandAliasesEnabled = false;
854
790
  }
855
- return result;
856
- }
857
-
858
- function padArgsWithUndefinedUntilExpectedLength(cmd, args) {
859
- while(args.length < cmd.arguments.length)args.push(undefined);
860
- return args;
861
- }
862
-
863
- function parseArguments(cmd, args) {
864
- const $ = cmd.$;
865
- const last = $.registeredArguments.length - 1;
866
- return args.map((arg, i)=>{
867
- if (!arg) return arg;
868
- const parse = cmd.meta.argParsers[i > last ? last : i];
869
- return parse ? parse(arg) : arg;
870
- });
871
- }
872
-
873
- function parsedValidArgsWithPresets(cmd, presetArgs) {
874
- const result = arrAssign([], ...presetArgs, parseArguments(cmd, cmd.$.args));
875
- combineVariadicArgs(cmd, result);
876
- assertValidArguments(cmd, result);
877
- return padArgsWithUndefinedUntilExpectedLength(cmd, result);
878
- }
879
-
880
- /**
881
- * Validate ALREADY PARSED options using the validators defined in the command builder.
882
- */ function assertValidOptions(cb, parsedOptions) {
883
- for (const [key, value] of Object.entries(parsedOptions)){
884
- if (!cb.meta.optValidators[key]) continue;
885
- if (value == null) continue;
886
- for (const isValid of cb.meta.optValidators[key]){
887
- assertThat(value, isValid);
888
- }
791
+ /**
792
+ * Inherits the feature settings from a parent CommandFeatureSelector instance.
793
+ * @param parentFeatures - The parent CommandFeatureSelector instance to inherit from.
794
+ */ inheritFrom(parentFeatures) {
795
+ if (!parentFeatures) return;
796
+ if (this.cmd.meta.isNative) this.isAutoAssignMissingOptionFlagsEnabled = true;
797
+ else this.isAutoAssignMissingOptionFlagsEnabled = parentFeatures.isAutoAssignMissingOptionFlagsEnabled;
798
+ this.isAutoAssignSubCommandAliasesEnabled = parentFeatures.isAutoAssignSubCommandAliasesEnabled;
799
+ this.isPresetsEnabled = parentFeatures.isPresetsEnabled;
800
+ }
801
+ /**
802
+ * Enables or disables the app data feature.
803
+ * @param boolean - Indicates whether the app data feature should be enabled or disabled.
804
+ * @returns The current CommandFeatureSelector instance.
805
+ * @throws Error if the command is native and the app data feature is being configured.
806
+ */ appData(boolean = true) {
807
+ return this.setProperty(boolean, 'isAppDataEnabled', 'appData', true);
808
+ }
809
+ /**
810
+ * Enables or disables the config feature.
811
+ * @param boolean - Indicates whether the config feature should be enabled or disabled.
812
+ * @returns The current CommandFeatureSelector instance.
813
+ * @throws Error if the command is native and the config feature is being configured.
814
+ */ config(boolean = true) {
815
+ return this.setProperty(boolean, 'isConfigEnabled', 'config', true);
816
+ }
817
+ /**
818
+ * Enables or disables the presets feature.
819
+ * @param boolean - Indicates whether the presets feature should be enabled or disabled.
820
+ * @returns The current CommandFeatureSelector instance.
821
+ * @throws Error if the command is native and the presets feature is being configured.
822
+ */ presets(boolean = true) {
823
+ return this.setProperty(boolean, 'isPresetsEnabled', 'presets', true);
824
+ }
825
+ /**
826
+ * Enables or disables the auto assign missing option flags feature.
827
+ * @param boolean - Indicates whether the auto assign missing option flags feature should be enabled or disabled.
828
+ * @returns The current CommandFeatureSelector instance.
829
+ */ autoAssignMissingOptionFlags(boolean = true) {
830
+ return this.setProperty(boolean, 'isAutoAssignMissingOptionFlagsEnabled', 'autoAssignMissingOptionFlags', false);
831
+ }
832
+ /**
833
+ * Enables or disables the auto assign sub command aliases feature.
834
+ * @param boolean - Indicates whether the auto assign sub command aliases feature should be enabled or disabled.
835
+ * @returns The current CommandFeatureSelector instance.
836
+ */ autoAssignSubCommandAliases(boolean = true) {
837
+ return this.setProperty(boolean, 'isAutoAssignSubCommandAliasesEnabled', 'autoAssignSubCommandAliases', false);
838
+ }
839
+ /**
840
+ * Helper method to enable or disable a feature.
841
+ * @param boolean - Indicates whether the auto assign sub command aliases feature should be enabled or disabled.
842
+ * @param prop - The property name to set.
843
+ * @param method - The method name to output in the debug message.
844
+ * @param throwIfNative - Indicates whether an error should be thrown if the command is native.
845
+ * @returns The current CommandFeatureSelector instance.
846
+ */ setProperty(boolean = true, prop, method, throwIfNative) {
847
+ if (this[prop] === boolean) return this;
848
+ if (throwIfNative && this.cmd.meta.isNative) throw new Error(`Cannot configure ${method} for native command.`);
849
+ Object.defineProperty(this, prop, {
850
+ value: boolean,
851
+ configurable: true,
852
+ writable: true,
853
+ enumerable: true
854
+ });
855
+ this.cmd.outputDebugMessage('featureEnabled', ()=>({
856
+ [method]: boolean
857
+ }));
858
+ return this;
889
859
  }
890
- return parsedOptions;
891
- }
892
-
893
- function getOwnAndGlobalOptions(cmd) {
894
- return cmd.options.concat(getGlobalOptions(cmd));
895
860
  }
896
861
 
897
- function deleteOptionsWithDefaultOrNoValue(cmd, opts) {
898
- const names = new Set(getOwnAndGlobalOptions(cmd).map((o)=>o.attributeName()));
899
- for (const [key, value] of Object.entries(opts)){
900
- if (!names.has(key) || value === false || value == null) {
901
- delete opts[key];
862
+ const DefaultHelpConfig = {
863
+ helpWidth: undefined,
864
+ showGlobalOptions: true,
865
+ sortSubcommands: false,
866
+ sortOptions: false,
867
+ subcommandTerm,
868
+ argumentTerm,
869
+ commandUsage,
870
+ visibleOptions,
871
+ visibleGlobalOptions,
872
+ subcommandDescription,
873
+ optionDescription,
874
+ argumentDescription,
875
+ commandDescription,
876
+ formatHelp
877
+ };
878
+ function formatHelp(cmd) {
879
+ let result = Help.prototype.formatHelp.call(this, cmd, this);
880
+ result = movePresetOptionsUnderOwnHeader(result);
881
+ result = rearrangeSections(result);
882
+ result = fixLinebreaks(result);
883
+ result = addColors(result);
884
+ return result;
885
+ function movePresetOptionsUnderOwnHeader(result) {
886
+ const lines = result.split('\n');
887
+ const presetLines = lines.filter((line)=>line.includes('[Preset]:'));
888
+ const firstPresetIndex = lines.findIndex((line)=>line.includes('[Preset]'));
889
+ if (firstPresetIndex !== -1) {
890
+ lines.splice(firstPresetIndex, presetLines.length);
891
+ lines.push('', 'Preset Options:', ...presetLines);
892
+ result = lines.join('\n');
902
893
  }
894
+ return result;
903
895
  }
904
- return opts;
905
- }
906
-
907
- /**
908
- * Parses (and validates) options using the parsers defined in the command builder.
909
- */ function parseOptions(cmd, opts) {
910
- for (const [key, value] of Object.entries(opts)){
911
- const parse = cmd.meta.optParsers[key];
912
- opts[key] = parse ? parse(value) : value;
896
+ function rearrangeSections(result) {
897
+ const order = [
898
+ 'Usage:',
899
+ 'Version:',
900
+ 'Description:',
901
+ 'Commands:',
902
+ 'Arguments:',
903
+ 'Options:',
904
+ 'Global Options:',
905
+ 'Preset Options:'
906
+ ];
907
+ const blocks = {};
908
+ let lastAddedBlock = 'Description:';
909
+ result.split(/\n\n+/).forEach((block)=>{
910
+ const title = block.split(':')[0].trim() + ':';
911
+ if (/^[A-Z][\w ]+:/.test(title)) {
912
+ blocks[title] = block;
913
+ lastAddedBlock = title;
914
+ if (!order.includes(title)) order.push(title);
915
+ } else {
916
+ if (!order.includes(lastAddedBlock)) order.push(lastAddedBlock);
917
+ blocks[lastAddedBlock] = ((blocks[lastAddedBlock] || '') + '\n\n' + block).trim();
918
+ }
919
+ });
920
+ result = order.map((title)=>blocks[title]).join('\n\n').trim();
921
+ return result;
922
+ }
923
+ function fixLinebreaks(result) {
924
+ result = '\n' + result.trim() + '\n\n';
925
+ result = result.replace(/\n\n+/g, '\n\n');
926
+ return result;
927
+ }
928
+ function addColors(result) {
929
+ result = result.replace(/\|/g, colors.gray(colors.dim('|')));
930
+ result = result.replace(/^[A-Z][\w ]+:/gm, (s)=>colors.yellow(s));
931
+ return result;
913
932
  }
914
- return opts;
915
933
  }
916
-
917
- function optsWithGlobalsParsed(cmd) {
918
- const $ = cmd.$;
919
- return parseOptions(cmd, $.optsWithGlobals());
934
+ function subcommandTerm(cmd) {
935
+ const args = cmd.registeredArguments.map((arg)=>this.argumentTerm(arg)).join(' ');
936
+ const parent = cmd.parent || cmd;
937
+ const hasAliases = !cmd.parent || parent.commands.some((c)=>!!c.alias());
938
+ if (!hasAliases) return cmd.name() + (args ? ' ' + args : '');
939
+ const padsize = Math.max(1, ...parent.commands.map((c)=>c.alias()?.length || 1));
940
+ let alias = cmd.alias() || ' ';
941
+ alias = alias.padEnd(padsize, ' ') + ' |';
942
+ return alias + cmd.name() + (args ? ' ' + args : '');
943
+ }
944
+ function argumentTerm(arg) {
945
+ let r = arg.name();
946
+ const rest = arg.variadic ? '...' : '';
947
+ if (arg.required) r = '<' + r + rest + '>';
948
+ else r = '[' + r + rest + ']';
949
+ return r;
950
+ }
951
+ function commandUsage(cmd) {
952
+ const args = cmd.registeredArguments.map((arg)=>this.argumentTerm(arg)).join(' ');
953
+ return [
954
+ cmd.builder.getPrefixString() + ' ' + (args ? args : '[command]'),
955
+ '[options]'
956
+ ].join(' ');
920
957
  }
921
-
922
- function parsedValidOptsWithPresets(cb, presetOpts) {
923
- const parsed = optsWithGlobalsParsed(cb);
924
- const opts = presetOpts.length ? Object.assign({}, ...presetOpts, parsed) : parsed;
925
- deleteOptionsWithDefaultOrNoValue(cb, opts);
926
- assertValidOptions(cb, opts);
927
- return opts;
958
+ function visibleOptions(cmd) {
959
+ const builder = cmd.builder;
960
+ const opts = cmd.options.filter((opt)=>!opt.hidden && !builder.meta.globalOptions.includes(opt));
961
+ const presets = opts.filter((opt)=>opt.description.startsWith('[Preset]'));
962
+ return opts.filter((opt)=>!presets.includes(opt)).concat(presets);
928
963
  }
929
-
930
- function parsedValidArgsOptsWithPresets(cmd) {
931
- const [presetArgs, presetOpts, presetOrder] = getPresetArgsAndOpts(cmd);
932
- const args = parsedValidArgsWithPresets(cmd, presetArgs);
933
- const opts = parsedValidOptsWithPresets(cmd, presetOpts);
934
- debugLogArgsOpts(cmd, args, opts, presetArgs, presetOpts, presetOrder);
935
- return [
936
- args,
937
- opts
938
- ];
964
+ function visibleGlobalOptions(cmd) {
965
+ const gopts = cmd.builder.getGlobalOptions();
966
+ if (gopts.find((opt)=>opt.long === '--help')) return gopts;
967
+ const help = cmd.options.find((opt)=>opt.long === '--help');
968
+ if (help) gopts.push(help);
969
+ return gopts;
939
970
  }
940
-
941
- /**
942
- *
943
- */ function actionWrapper(cmd) {
944
- const meta = cmd.meta;
945
- cmd.$.action(async ()=>{
946
- handleOutputOptions(cmd);
947
- const [args, opts] = parsedValidArgsOptsWithPresets(cmd);
948
- if (opts['help'] || !meta.actionHandler) return cmd.outputHelp();
949
- return await meta.actionHandler(...args, opts, cmd).catch((err)=>handleError(cmd, err));
950
- });
971
+ function subcommandDescription(cmd) {
972
+ return colors.gray(Help.prototype.subcommandDescription.call(this, cmd));
951
973
  }
952
-
953
- function getClosestNonNativeParent(cmd) {
954
- for (const anc of walkAncestors(cmd, {
955
- includeSelf: true
956
- })){
957
- if (!anc.meta.isNative) return anc;
958
- }
959
- return cmd.root;
974
+ function optionDescription(opt) {
975
+ return colors.gray(Help.prototype.optionDescription.call(this, opt));
960
976
  }
961
-
962
- function addConfigCommands(cmd) {
963
- cmd.nativeCommand('config', createConfig);
977
+ function argumentDescription(arg) {
978
+ return colors.gray(Help.prototype.argumentDescription.call(this, arg));
979
+ }
980
+ function commandDescription(cmd) {
981
+ const v = cmd.builder.getVersion();
982
+ const version = v ? colors.yellow('Version: ') + v + '\n\n' : '';
983
+ const description = 'Description:\n' + Help.prototype.commandDescription.call(this, cmd);
984
+ return version + description;
964
985
  }
965
- const createConfig = (()=>{
966
- return function createConfig(config) {
967
- config.description('Manage configuration file.');
968
- config.alias('c');
969
- config.nativeCommand('edit', createEdit);
970
- config.nativeCommand('list', createList);
971
- config.nativeCommand('get', createGet);
972
- config.nativeCommand('set', createSet);
973
- config.nativeCommand('reset', createReset);
974
- };
975
- function createEdit(edit) {
976
- edit.description('Edit as JSON in a text editor.');
977
- edit.option('--editor [cmd]', (e)=>{
978
- e.description('The command to launch your preferred text editor.');
979
- e.default(defaultOpenInEditorCommand());
980
- });
981
- edit.action(editAction);
982
- }
983
- async function editAction(opts, edit) {
984
- const cmd = getClosestNonNativeParent(edit);
985
- cmd.db.config.edit(opts.editor);
986
- console.info(cmd.db.config.getAll());
987
- }
988
- function createList(list) {
989
- list.description('Print entire config with values, descriptions and defaults.');
990
- list.action(listAction);
991
- }
992
- async function listAction(_, list) {
993
- const cmd = getClosestNonNativeParent(list);
994
- console.dir(cmd.db.config.keys.map((key)=>{
995
- return {
996
- key,
997
- description: cmd.db.config.descriptions[key],
998
- value: cmd.db.config.get(key),
999
- defaultValue: cmd.db.config.defaultValues
1000
- };
1001
- }));
1002
- }
1003
- function createGet(get) {
1004
- get.description('Print value(s) from the config.');
1005
- get.argument('[key]', (a)=>{
1006
- a.description('The key to print the value of. Omit to print all values.');
1007
- a.choices(a.cmd.db.config.keys);
1008
- });
1009
- get.action(getAction);
1010
- }
1011
- async function getAction(key, get) {
1012
- const cmd = getClosestNonNativeParent(get);
1013
- if (key) console.log(cmd.db.config.get(key));
1014
- else console.log(cmd.db.config.getAll());
1015
- }
1016
- function createSet(set) {
1017
- set.description('Set a value in the config.');
1018
- set.argument('<key>', (a)=>{
1019
- a.description('The key to set the value of.');
1020
- a.choices(a.cmd.db.config.keys);
1021
- });
1022
- set.argument('<value>', (a)=>a.description('The new value.'));
1023
- set.action(setAction);
1024
- }
1025
- async function setAction(key, value, set) {
1026
- const cmd = getClosestNonNativeParent(set);
1027
- const from = cmd.db.config.get(key);
1028
- const to = cmd.db.config.parsers[key](value);
1029
- cmd.db.config.set(key, to);
1030
- console.info({
1031
- changed: key,
1032
- from,
1033
- to
1034
- });
1035
- }
1036
- function createReset(reset) {
1037
- reset.description('Reset to defaults.');
1038
- reset.argument('[key]', (a)=>{
1039
- a.description('The key for which to reset the value. Omit to reset entire config.');
1040
- a.choices(a.cmd.db.config.keys);
1041
- });
1042
- reset.action(resetAction);
1043
- }
1044
- async function resetAction(key, reset) {
1045
- var _reset_parent;
1046
- const cmd = (_reset_parent = reset.parent) == null ? void 0 : _reset_parent.parent;
1047
- if (key) cmd.db.config.reset(key);
1048
- else cmd.db.config.resetAll();
1049
- console.info(cmd.db.config.getAll());
1050
- }
1051
- })();
1052
-
1053
- function addPresetsCommands(cmd) {
1054
- cmd.nativeCommand('presets', createPresets);
1055
- }
1056
- const createPresets = (()=>{
1057
- return function createPresets(presets) {
1058
- const cmd = getClosestNonNativeParent(presets);
1059
- for (const [name, pre] of Object.entries(cmd.db.presets.getAll())){
1060
- if (name === 'defaults') continue;
1061
- cmd.option(`--${name}`, (opt)=>{
1062
- opt.description('[Preset]: ' + pre.description);
1063
- if (pre.presets.length) {
1064
- opt.implies(pre.presets.reduce((acc, key)=>{
1065
- acc[key] = true;
1066
- return acc;
1067
- }, {}));
1068
- }
1069
- });
1070
- }
1071
- presets.alias('p');
1072
- presets.description('Edit presets in your text editor', '', 'A preset consists of pre-set arguments and/or options for a command.', 'Additionally, a preset can have other presets as dependencies.', 'When running the command, multiple presets can be stacked.', 'Required arguments cannot be pre-set.');
1073
- presets.nativeCommand('edit', createEdit);
1074
- presets.nativeCommand('list', createList);
1075
- };
1076
- function createEdit(edit) {
1077
- edit.description('Edit as JSON in a text editor.');
1078
- edit.option('--editor [cmd]', (e)=>{
1079
- e.description('The command to launch your preferred text editor.');
1080
- e.default(defaultOpenInEditorCommand());
1081
- });
1082
- edit.action(editAction);
1083
- }
1084
- async function editAction(opts, edit) {
1085
- const cmd = getClosestNonNativeParent(edit);
1086
- cmd.db.presets.edit(opts.editor);
1087
- console.info(cmd.db.presets.getAll());
1088
- }
1089
- function createList(list) {
1090
- list.description('List all presets.');
1091
- list.action(listAction);
1092
- }
1093
- async function listAction(_, list) {
1094
- const cmd = getClosestNonNativeParent(list);
1095
- console.dir(cmd.db.presets.getAll(), {
1096
- depth: null
1097
- });
1098
- }
1099
- })();
1100
986
 
1101
987
  function formatTableForTerminal(rows, headers) {
1102
988
  if (!rows.length || !rows[0].length) return '';
@@ -1108,527 +994,264 @@ function formatTableForTerminal(rows, headers) {
1108
994
  return table.toString();
1109
995
  }
1110
996
 
1111
- /**
1112
- * Get a commands prefix array based on all its parent/ancestor commands.
1113
- */ function prefixArray(cmd) {
1114
- return getAncestors(cmd, {
1115
- includeSelf: true
1116
- }).reverse().map((node)=>node.name);
1117
- }
997
+ const isArray = Array.isArray;
1118
998
 
1119
- function getJsonFilepath(cmd) {
1120
- return path.join(CommandBuilder.dataDirectory, prefixArray(cmd).join('-')) + '.json';
999
+ /**
1000
+ * Checks if the provided value is an object (null, arrays and functions not included).
1001
+ * @template T - The type of the value to check.
1002
+ * @param value The value to check.
1003
+ * @returns A boolean indicating whether the provided value is an object.
1004
+ * @example ```ts
1005
+ * isObject({});;
1006
+ * //=> true
1007
+ * isObject([1]);;
1008
+ * //=> false
1009
+ * isObject(123);;
1010
+ * //=> false
1011
+ * ```
1012
+ */ function isObject(value) {
1013
+ return value != null && typeof value === 'object' && !Array.isArray(value);
1121
1014
  }
1122
1015
 
1123
1016
  /**
1124
- * Get a commands prefix string based on all its parent/ancestor commands.
1125
- */ function prefixString(cmd) {
1126
- return prefixArray(cmd).join(' ');
1017
+ * Determine whether the input is a string array.
1018
+ */ function isStringArray(value) {
1019
+ return Array.isArray(value) && value.every((v)=>isString(v));
1127
1020
  }
1128
1021
 
1129
- function* walkChildren(cmd, options) {
1130
- var _options;
1131
- if ((_options = options) == null ? void 0 : _options.includeSelf) yield cmd;
1132
- for (const sub of cmd.meta.subcommands){
1133
- yield sub;
1134
- yield* walkChildren(sub);
1135
- }
1022
+ function isStringWithNoSpacesOrDashes(value) {
1023
+ return isString(value) && /^[^\s-]+$/i.test(value);
1136
1024
  }
1137
1025
 
1138
1026
  /**
1139
- * Returns a command's and its children's prefix strings.
1140
- */ function prefixStringsRecursive(cmd, filter) {
1141
- const result = [];
1142
- for (const c of walkChildren(cmd, {
1143
- includeSelf: true
1144
- })){
1145
- const prefix = prefixString(c);
1146
- if (filter && !filter(prefix, c)) continue;
1147
- result.push([
1148
- prefix,
1149
- c.get.summary
1150
- ]);
1151
- }
1152
- return result;
1153
- }
1154
-
1155
- function addUtilCommands(cmd) {
1156
- cmd.nativeCommand('util', createUtil);
1027
+ * Checks if the current platform is OSX.
1028
+ * It checks the 'process' object and the 'platform' property to determine if the platform is 'darwin'.
1029
+ * @returns A boolean indicating whether the current platform is OSX.
1030
+ */ function isOSX() {
1031
+ return process.platform === 'darwin';
1157
1032
  }
1158
- const createUtil = (()=>{
1159
- return function createUtil(util) {
1160
- util.$.alias('u');
1161
- util.description('Utility commands.');
1162
- const cmd = getClosestNonNativeParent(util);
1163
- if (cmd.features.isConfigEnabled) {
1164
- addConfigCommands(util);
1165
- }
1166
- if (cmd.features.isPresetsEnabled && cmd.meta.actionHandler) {
1167
- addPresetsCommands(util);
1168
- }
1169
- if (hasGrandChildren(cmd) || usesJsonFile(cmd)) {
1170
- util.nativeCommand('tree', createTree);
1171
- }
1172
- if (usesJsonFile(cmd)) {
1173
- util.nativeCommand('filepath', createFilepath);
1174
- }
1175
- };
1176
- function hasGrandChildren(c) {
1177
- return c.meta.subcommands.some((c)=>!!c.meta.subcommands.length);
1178
- }
1179
- function usesJsonFile(c) {
1180
- return c.features.isConfigEnabled || c.features.isPresetsEnabled;
1181
- }
1182
- function createFilepath(fp) {
1183
- fp.description('Print filepath to JSON file containing user data, eg. config and presets.');
1184
- fp.action(filepathAction);
1185
- }
1186
- async function filepathAction(_, fp) {
1187
- const cmd = getClosestNonNativeParent(fp);
1188
- console.log(getJsonFilepath(cmd));
1189
- }
1190
- function createTree(tree) {
1191
- tree.description('List nested subcommands.');
1192
- tree.action(treeAction);
1193
- tree.nativeCommand('all', createTreeAll);
1194
- }
1195
- async function treeAction(_, tree) {
1196
- const cmd = getClosestNonNativeParent(tree);
1197
- const table = prefixStringsRecursive(cmd, (prefix)=>{
1198
- return !/ (config|presets|util)( .+)?$/gi.test(prefix);
1199
- });
1200
- console.log(formatCommandTable(table));
1201
- }
1202
- function createTreeAll(all) {
1203
- all.description('List all subcommands including utility commands.');
1204
- all.action(treeAllAction);
1205
- }
1206
- async function treeAllAction(_, all) {
1207
- const cmd = getClosestNonNativeParent(all);
1208
- const table = prefixStringsRecursive(cmd);
1209
- console.log(formatCommandTable(table));
1210
- }
1211
- function formatCommandTable(table) {
1212
- const ansi = table.map((row)=>{
1213
- const arr = row[0].split(' ');
1214
- const last = arr.pop();
1215
- let col = colors.magenta;
1216
- if (row[1].startsWith('[Preset]')) {
1217
- col = colors.green;
1218
- } else if (/ (util|config|presets) /.test(row[0])) {
1219
- col = colors.gray;
1220
- } else if (/ (util|config|presets)/.test(row[0])) {
1221
- col = colors.dim;
1222
- }
1223
- row[0] = arr.map(colors.dim).concat(col(last)).join(' ');
1224
- return row;
1225
- });
1226
- return formatTableForTerminal(ansi, [
1227
- 'Command',
1228
- 'Summary'
1229
- ]);
1230
- }
1231
- })();
1232
1033
 
1233
- function assertNoDuplicateCommandNames(cmd) {
1234
- const names = cmd.$.commands.map((sub)=>sub.aliases().concat(sub.name())).flat();
1235
- if (names.length !== new Set(names).size) {
1236
- throw new Error(`Duplicate subcommand names/aliases found for command, ${cmd.name}: ${names.join(', ')}`);
1034
+ /**
1035
+ * Returns whether Visual Studio Code is installed on the system.
1036
+ * @example isVsCodeInstalled() //=> true
1037
+ */ function isVsCodeInstalled() {
1038
+ try {
1039
+ const stdout = execSync('code --help').toString();
1040
+ return stdout.startsWith('Visual Studio Code') && stdout.includes('-w --wait');
1041
+ } catch (e) {
1042
+ return false;
1237
1043
  }
1238
1044
  }
1239
1045
 
1240
- function assertNoDuplicateOptionNames(cmd) {
1241
- const throwErr = (cmd, opt, anc)=>{
1242
- throw new Error(`Duplicate option names > cmd: ${cmd.name}, ${anc ? `anc: ${anc.name}, ` : ''}opt: ${opt}`);
1243
- };
1244
- const set = new Set();
1245
- for (const opt of cmd.options){
1246
- if (opt.name() === 'help') continue;
1247
- if (opt.short) {
1248
- if (set.has(opt.short)) throwErr(cmd, opt.short);
1249
- set.add(opt.short);
1250
- }
1251
- if (opt.long) {
1252
- if (set.has(opt.long)) throwErr(cmd, opt.long);
1253
- set.add(opt.long);
1254
- }
1255
- if (opt.attributeName()) {
1256
- if (set.has(opt.attributeName())) throwErr(cmd, opt.attributeName());
1257
- set.add(opt.attributeName());
1258
- }
1259
- }
1260
- for (const anc of walkAncestors(cmd)){
1261
- for (const opt of anc.$.options){
1262
- if (opt.short && set.has(opt.short)) {
1263
- if (opt.short !== 'V') continue;
1264
- throwErr(cmd, opt.short, anc);
1265
- }
1266
- if (opt.long && set.has(opt.long)) {
1267
- throwErr(cmd, opt.long, anc);
1268
- }
1269
- if (opt.attributeName() && set.has(opt.attributeName())) {
1270
- throwErr(cmd, opt.attributeName(), anc);
1271
- }
1272
- }
1273
- }
1046
+ /**
1047
+ * Checks if the current platform is Windows.
1048
+ * @remarks
1049
+ * It checks the 'process' object and the 'platform' property to determine if the platform is 'win32'.
1050
+ * It also checks the 'OSTYPE' environment variable to see if it matches 'msys' or 'cygwin'.
1051
+ * @returns A boolean indicating whether the current platform is Windows.
1052
+ */ function isWindows() {
1053
+ return isWin;
1274
1054
  }
1055
+ const isWin = process && (process.platform === 'win32' || /^(msys|cygwin)$/.test(process.env['OSTYPE'] || ''));
1275
1056
 
1276
1057
  /**
1277
- * Extract the argument name from an option's 'flags' string.
1278
- */ function getOptionArgumentName(opt) {
1279
- const result = arrLast(opt.flags.trim().split(' '));
1280
- if (/-/.test(result)) return undefined;
1281
- return result;
1058
+ * Get the default command to open a file in in a text editor.
1059
+ * If VSCode is installed, this is used. Otherwise, the default text editor of the OS is used.
1060
+ */ function defaultOpenInEditorCommand() {
1061
+ if (!COMMAND) COMMAND = isVsCodeInstalled() ? 'code -w' : isWindows() ? 'notepad' : isOSX() ? 'open vi' : 'xdg-open';
1062
+ return COMMAND;
1282
1063
  }
1064
+ let COMMAND = '';
1283
1065
 
1284
1066
  /**
1285
- * Update an Option's 'flags' property from its 'short' and 'long' properties.
1286
- * The flags property is not automatically updated when 'short' or 'long' are changed.
1287
- */ function renderOptionFlags(opt) {
1288
- const shortLong = [];
1289
- if (opt.short) shortLong.push(opt.short);
1290
- if (opt.long) shortLong.push(opt.long);
1291
- const argName = getOptionArgumentName(opt);
1292
- return shortLong.join(', ') + (argName ? ' ' + argName : '');
1067
+ * Reads a file and returns the file's contents.
1068
+ * Identical to fs.readFileSync, except that it uses utf8 encoding by default.
1069
+ *
1070
+ * @param filepath - The path to the file.
1071
+ * @param encoding - The encoding to use when reading the file.
1072
+ * @returns The file's contents.
1073
+ */ function readFileSync(filepath, encoding = 'utf8') {
1074
+ return fs.readFileSync(filepath, encoding);
1293
1075
  }
1294
1076
 
1295
1077
  /**
1296
- * Set an Option's 'short' name. The 'flags' property is updated accordingly.
1297
- * The '-' prefix is automatically added if not present.
1298
- */ function setOptionShortName(opt, short) {
1299
- opt.short = strEnsureStartsWith(short, '-').replace(/^-+/, '-');
1300
- opt.flags = renderOptionFlags(opt);
1078
+ * Ensures that a string starts with a specified substring. If the string already starts with the specified substring, it is returned as is. Otherwise, the substring is appended to the end of the string.
1079
+ * @param string The string to be processed.
1080
+ * @param startsWith The substring that the string should end with.
1081
+ * @example ```ts
1082
+ * strEnsureStartsWith('json', '.');
1083
+ * //=> '.json'
1084
+ * strEnsureStartsWith('.json', '.');
1085
+ * //=> '.json'
1086
+ * ```
1087
+ */ function strEnsureStartsWith(string, startsWith) {
1088
+ return string.startsWith(startsWith) ? string : startsWith + string;
1301
1089
  }
1302
1090
 
1303
1091
  /**
1304
- * Automatically set 'short' and 'long' names to options that don't have one assigned yet.
1305
- *
1306
- * First, it tries to assign a short name based on the first letter of the option's attribute name
1307
- * Both lower and upper case are tried. If these is not available, the next letter of the option name is tried.
1092
+ * Returns a path to the os tmpdir location.
1308
1093
  *
1309
- * If none of the letters of the option name are available, the option is skipped until all other
1310
- * options have had letters from their names attempted assigned.
1311
- * Those that remain are assigned the first available letter of the alphabet + 0-9.
1312
- * If there are 64 options for the command and no more alphanumeric characters are available,
1313
- * the option is not assigned a short name.
1314
- */ function autoAssignMissingOptionFlags(cmd) {
1315
- const taken = new Set();
1316
- for (const anc of walkAncestors(cmd, {
1317
- includeSelf: true
1318
- })){
1319
- anc.options.forEach((opt)=>{
1320
- if (!opt.short) return;
1321
- taken.add(opt.short.replace(/^-/g, ''));
1322
- });
1323
- }
1324
- const failed = new Set();
1325
- // assign letter from option name
1326
- cmd.options.forEach((opt)=>{
1327
- if (opt.short) return;
1328
- const name = opt.attributeName();
1329
- for(let c = 0; c < name.length; c++){
1330
- let char = name.charAt(c).toLowerCase();
1331
- if (taken.has(char)) {
1332
- char = char.toUpperCase();
1333
- if (taken.has(char)) continue;
1334
- }
1335
- setOptionShortName(opt, char);
1336
- taken.add(char);
1337
- return;
1338
- }
1339
- failed.add(opt);
1340
- });
1341
- // assign random alphanumeric character.
1342
- const name = 'abcdefghijklmnopqrstuvwxyz1234567890';
1343
- failed.forEach((opt)=>{
1344
- for(let c = 0; c < name.length; c++){
1345
- let char = name.charAt(c);
1346
- if (taken.has(char)) {
1347
- char = char.toUpperCase();
1348
- if (taken.has(char)) continue;
1349
- }
1350
- setOptionShortName(opt, char);
1351
- taken.add(char);
1352
- return;
1353
- }
1354
- });
1094
+ * @param paths - The paths to join to the os tmpdir location.
1095
+ */ function getTempDataPath(...paths) {
1096
+ return path.join(tempdir, ...paths);
1355
1097
  }
1098
+ const tempdir = fs.realpathSync(os.tmpdir());
1356
1099
 
1357
1100
  /**
1358
- * Iterate sibling CommandBuilder objects.
1359
- */ function* walkSiblings(cmd) {
1360
- if (!cmd.parent) return;
1361
- for (const sub of cmd.parent.meta.subcommands){
1362
- if (sub === cmd) continue;
1363
- yield sub;
1101
+ * Syncrhonously creates a temporary file and deletes it after the callback has finished.
1102
+ * @param fileExtension - The file extension to use for the temporary file.
1103
+ * @param callback - The callback to execute with the temporary file path. The callback can return a promise and the temporary file will not be deleted until the promise has resolved or rejected.
1104
+ */ function tempFileSync(fileExtension, callback) {
1105
+ const fpath = getTempDataPath('temp', Date.now() + strEnsureStartsWith(fileExtension, '.'));
1106
+ fs.ensureFileSync(fpath);
1107
+ try {
1108
+ const retval = callback(fpath);
1109
+ fs.remove(fpath);
1110
+ return retval;
1111
+ } catch (error) {
1112
+ fs.remove(fpath);
1364
1113
  }
1365
1114
  }
1366
1115
 
1367
1116
  /**
1368
- * Returns an array of sibling CommandBuilder objects.
1369
- */ function getSiblings(cmd) {
1370
- return [
1371
- ...walkSiblings(cmd)
1372
- ];
1373
- }
1374
-
1375
- /**
1376
- * Makes aliases for the command.
1377
- * The idea is to be able to navigate the command tree by only typing the first letter(s) of the command names.
1378
- *
1379
- * Example: A command 'cola' would get these aliases: ['c', 'co', 'col'].
1380
- * However, if there are namespace clashes with sibling subcommands that start with the same letter,
1381
- * eg. like 'cola' and 'coal' where the first two letters clash, cola's aliases are reduced to only ['col'] and similarly for 'coal'.
1117
+ * Prompts the user to edit a string in the user's text editor.
1382
1118
  *
1383
- * This method creates the aliases, ensuring there are no clashes with sublings, why it is important that the
1384
- * entire command tree is built before invoking this method.
1385
- */ function autoAssignSubCommandAliases(cmd) {
1386
- if (cmd.get.alias || cmd.name.length <= 1) return;
1387
- const sibAliases = getSiblings(cmd).map((sib)=>sib.get.aliases).flat();
1388
- for(let i = 0; i < cmd.name.length - 1; i++){
1389
- let cmdAlias = cmd.name.substring(0, i + 1);
1390
- let isClash = arrSome(sibAliases, (sibAlias)=>{
1391
- return cmdAlias === sibAlias;
1119
+ * @example ```ts
1120
+ * promptUserEditInTextEditor({ editor: 'notepad' })
1121
+ * ```
1122
+ */ function promptUserEditInTextEditorSync(options) {
1123
+ const { editor, content, extension } = applyDefaults(options);
1124
+ return tempFileSync(extension, (tempfile)=>{
1125
+ writeFileSync(tempfile, content);
1126
+ execSync(`${editor} ${tempfile}`, {
1127
+ stdio: 'inherit'
1392
1128
  });
1393
- if (isClash && i === 0) {
1394
- cmdAlias = cmdAlias.charAt(0).toUpperCase();
1395
- isClash = arrSome(sibAliases, (sibAlias)=>{
1396
- return cmdAlias === sibAlias;
1397
- });
1398
- }
1399
- if (isClash) continue;
1400
- cmd.alias(cmdAlias);
1401
- return;
1402
- }
1403
- }
1404
-
1405
- function configureHelp(cmd) {
1406
- const cmdr = cmd.$;
1407
- cmdr.addHelpCommand('?', 'show help');
1408
- cmdr.configureHelp(opts);
1129
+ return readFileSync(tempfile);
1130
+ });
1409
1131
  }
1410
- const opts = (()=>{
1132
+ function applyDefaults(options = {}) {
1133
+ const content = options.content ?? '';
1134
+ const extension = strEnsureStartsWith(options.extension ?? '.txt', '.');
1135
+ const editor = options.editor ?? defaultOpenInEditorCommand();
1411
1136
  return {
1412
- helpWidth: undefined,
1413
- showGlobalOptions: true,
1414
- sortSubcommands: false,
1415
- sortOptions: false,
1416
- subcommandTerm,
1417
- argumentTerm,
1418
- commandUsage,
1419
- visibleOptions,
1420
- visibleGlobalOptions,
1421
- subcommandDescription,
1422
- optionDescription,
1423
- argumentDescription,
1424
- commandDescription,
1425
- formatHelp
1137
+ content,
1138
+ extension,
1139
+ editor
1426
1140
  };
1427
- function formatHelp(cmd) {
1428
- let result = Help.prototype.formatHelp.call(this, cmd, this);
1429
- result = movePresetOptionsUnderOwnHeader(result);
1430
- result = rearrangeSections(result);
1431
- result = fixLinebreaks(result);
1432
- result = addColors(result);
1433
- return result;
1434
- function movePresetOptionsUnderOwnHeader(result) {
1435
- const lines = result.split('\n');
1436
- const presetLines = lines.filter((line)=>line.includes('[Preset]:'));
1437
- const firstPresetIndex = lines.findIndex((line)=>line.includes('[Preset]'));
1438
- if (firstPresetIndex !== -1) {
1439
- lines.splice(firstPresetIndex, presetLines.length);
1440
- lines.push('', 'Preset Options:', ...presetLines);
1441
- result = lines.join('\n');
1442
- }
1443
- return result;
1444
- }
1445
- function rearrangeSections(result) {
1446
- const order = [
1447
- 'Usage:',
1448
- 'Version:',
1449
- 'Description:',
1450
- 'Commands:',
1451
- 'Arguments:',
1452
- 'Options:',
1453
- 'Global Options:',
1454
- 'Preset Options:'
1455
- ];
1456
- const blocks = {};
1457
- let lastAddedBlock = 'Description:';
1458
- result.split(/\n\n+/).forEach((block)=>{
1459
- const title = block.split(':')[0].trim() + ':';
1460
- if (/^[A-Z][\w ]+:/.test(title)) {
1461
- blocks[title] = block;
1462
- lastAddedBlock = title;
1463
- if (!order.includes(title)) order.push(title);
1464
- } else {
1465
- if (!order.includes(lastAddedBlock)) order.push(lastAddedBlock);
1466
- blocks[lastAddedBlock] = ((blocks[lastAddedBlock] || '') + '\n\n' + block).trim();
1467
- }
1468
- });
1469
- result = order.map((title)=>blocks[title]).join('\n\n').trim();
1470
- return result;
1471
- }
1472
- function fixLinebreaks(result) {
1473
- result = '\n' + result.trim() + '\n\n';
1474
- result = result.replace(/\n\n+/g, '\n\n');
1475
- return result;
1476
- }
1477
- function addColors(result) {
1478
- result = result.replace(/\|/g, colors.gray(colors.dim('|')));
1479
- result = result.replace(/^[A-Z][\w ]+:/gm, (s)=>colors.yellow(s));
1480
- return result;
1481
- }
1482
- }
1483
- function subcommandTerm(cmd) {
1484
- const args = cmd.registeredArguments.map((arg)=>this.argumentTerm(arg)).join(' ');
1485
- const parent = cmd.parent || cmd;
1486
- const padsize = Math.max(1, ...parent.commands.map((c)=>{
1487
- var _c_alias;
1488
- return ((_c_alias = c.alias()) == null ? void 0 : _c_alias.length) || 1;
1489
- }));
1490
- let alias = cmd.alias() || ' ';
1491
- alias = alias.padEnd(padsize, ' ') + ' |';
1492
- return alias + cmd.name() + (args ? ' ' + args : '');
1493
- }
1494
- function argumentTerm(arg) {
1495
- let r = arg.name();
1496
- const rest = arg.variadic ? '...' : '';
1497
- if (arg.required) r = '<' + r + rest + '>';
1498
- else r = '[' + r + rest + ']';
1499
- return r;
1500
- }
1501
- function commandUsage(cmd) {
1502
- const args = cmd.registeredArguments.map((arg)=>this.argumentTerm(arg)).join(' ');
1503
- return [
1504
- prefixString(cmd.builder) + ' ' + (args ? args : '[command]'),
1505
- '[options]'
1506
- ].join(' ');
1507
- }
1508
- function visibleOptions(cmd) {
1509
- const builder = cmd.builder;
1510
- const opts = cmd.options.filter((opt)=>!opt.hidden && !builder.meta.globalOptions.includes(opt));
1511
- const presets = opts.filter((opt)=>opt.description.startsWith('[Preset]'));
1512
- return opts.filter((opt)=>!presets.includes(opt)).concat(presets);
1513
- }
1514
- function visibleGlobalOptions(cmd) {
1515
- return getGlobalOptions(cmd.builder);
1516
- }
1517
- function subcommandDescription(cmd) {
1518
- return colors.gray(Help.prototype.subcommandDescription.call(this, cmd));
1519
- }
1520
- function optionDescription(opt) {
1521
- return colors.gray(Help.prototype.optionDescription.call(this, opt));
1522
- }
1523
- function argumentDescription(arg) {
1524
- return colors.gray(Help.prototype.argumentDescription.call(this, arg));
1525
- }
1526
- function commandDescription(cmd) {
1527
- const v = cmd.builder.get.version;
1528
- const version = v ? colors.yellow('Version: ') + v + '\n\n' : '';
1529
- const description = 'Description:\n' + Help.prototype.commandDescription.call(this, cmd);
1530
- return version + description;
1531
- }
1532
- })();
1533
-
1534
- function ensureBackRefToCommandBuilder(cmd) {
1535
- commandToBuilderMap.set(cmd.$, cmd);
1536
- }
1537
- const commandToBuilderMap = new WeakMap();
1538
- Object.defineProperty(Command.prototype, 'builder', {
1539
- get () {
1540
- const ins = commandToBuilderMap.get(this);
1541
- if (!ins) throw new Error(`CommandBuilder not found for command ${this.name()}`);
1542
- return ins;
1543
- }
1544
- });
1545
-
1546
- function initializeCommand(cmd, callback) {
1547
- const t0_precb = Date.now();
1548
- ensureBackRefToCommandBuilder(cmd);
1549
- actionWrapper(cmd);
1550
- configureHelp(cmd);
1551
- inheritBasicSettings(cmd);
1552
- inheritHiddenGlobalOptions(cmd);
1553
- const precb = Date.now() - t0_precb;
1554
- const t0_cb = Date.now();
1555
- if (callback) callback(cmd);
1556
- const cb = Date.now() - t0_cb;
1557
- const t0_postcb = Date.now();
1558
- if (cmd.features.isUtilsEnabled) addUtilCommands(cmd);
1559
- if (cmd.features.isAutoAssignSubCommandAliasesEnabled) {
1560
- autoAssignSubCommandAliases(cmd);
1561
- }
1562
- if (cmd.features.isAutoAssignMissingOptionFlagsEnabled) {
1563
- autoAssignMissingOptionFlags(cmd);
1564
- }
1565
- assertNoDuplicateCommandNames(cmd);
1566
- assertNoDuplicateOptionNames(cmd);
1567
- const postcb = Date.now() - t0_postcb;
1568
- if (precb + cb + postcb > 10) cmd.outputDebugInfo('init timer', ()=>({
1569
- precb,
1570
- cb,
1571
- postcb
1572
- }));
1573
- return cmd;
1574
- }
1575
- function inheritBasicSettings(cmd) {
1576
- if (!cmd.parent) return;
1577
- const cmdr = cmd.$;
1578
- if (cmdr.parent) cmdr.copyInheritedSettings(cmdr.parent);
1579
- }
1580
- function inheritHiddenGlobalOptions(cmd) {
1581
- if (!cmd.parent) return;
1582
- for (const opt of cmd.parent.meta.hiddenGlobalOptions){
1583
- cmd.meta.hiddenGlobalOptions.add(opt);
1584
- }
1585
1141
  }
1586
1142
 
1587
1143
  /**
1588
- * An error class for errors related to the JSON file used as simple database.
1589
- */ class JsonFileError extends XtError {
1144
+ * Edit a JSON-stringify-compatible value in the user's editor and return the (JSON.parse'd) result.
1145
+ *
1146
+ * @param value - The value to edit (NOT a json string). Defaults to an empty object.
1147
+ * @param editor - Launch command to start your editor. Defaults to VSCode: 'code -w' (if installed).
1148
+ * - Otherwise this logic: isWindows() ? 'notepad' : isOSX() ? 'open vi' : 'xdg-open'
1149
+ *
1150
+ * @example ```ts
1151
+ * promptUserEditJsonInTextEditorSync([1, 2])
1152
+ * ```
1153
+ */ function promptUserEditJsonInTextEditorSync(value = {}, editor) {
1154
+ const json = promptUserEditInTextEditorSync({
1155
+ editor,
1156
+ content: JSON.stringify(value, null, 2),
1157
+ extension: '.json'
1158
+ });
1159
+ return JSON.parse(json);
1590
1160
  }
1591
1161
 
1592
1162
  /**
1593
- * A class that represents the a section of the JSON file used as simple database.
1594
- */ class AbstractJsonFileSection extends Base {
1595
- get defaultValues() {
1596
- return this._defaultValues;
1163
+ * A class that represents a section of the JSON file used as a simple database.
1164
+ */ class AbstractJsonFileSection {
1165
+ /**
1166
+ * Creates an instance of AbstractJsonFileSection.
1167
+ * @param file - The parent JsonFile instance.
1168
+ * @param name - The name of the section.
1169
+ * @param keysAreFixed - Indicates whether the keys in the section are fixed.
1170
+ */ constructor(file, name, keysAreFixed){
1171
+ this.file = file;
1172
+ this.name = name;
1173
+ this.keysAreFixed = keysAreFixed;
1174
+ this.isInitialized = false;
1175
+ this.defaultValues = {};
1176
+ this.prefixBaseString = file.cmd.getPrefixArray().join('_');
1177
+ }
1178
+ /**
1179
+ * The JsonDB associated with the section.
1180
+ */ get db() {
1181
+ return this.file.db;
1182
+ }
1183
+ /**
1184
+ * The CommandBuilder associated with the section.
1185
+ */ get cmd() {
1186
+ return this.file.cmd;
1597
1187
  }
1598
1188
  /**
1599
- * Get the db lookup prefix for this section.
1600
- * @param key An optional key to append to the prefix.
1189
+ * Saves the section.
1190
+ * @returns A promise that resolves when the section is saved.
1191
+ */ async save() {
1192
+ return await this.db.save();
1193
+ }
1194
+ /**
1195
+ * Gets the object path prefix as dot-separated keys for this section of the JSON file database.
1196
+ * @param key - An optional key to append to the prefix path.
1197
+ * @returns The object path prefix.
1198
+ * @throws An error if the keys are fixed and the specified key does not exist in the default values.
1601
1199
  */ prefix(key) {
1602
- if (this.hasFixedKeysForUser && key && !Object.hasOwn(this.defaultValues, key)) {
1603
- throw new JsonFileError(`No config entry with key '${key}'`);
1200
+ if (this.keysAreFixed && key && !Object.hasOwn(this.defaultValues, key)) {
1201
+ throw new Error(`No entry with key '${key}'`);
1604
1202
  }
1605
- return this.name + (key ? '.' + key : '');
1203
+ return this.prefixBaseString + '.' + this.name + (key ? '.' + key : '');
1606
1204
  }
1607
- get(key) {
1205
+ /**
1206
+ * Gets the value associated with the specified key.
1207
+ * @param key - The key to get the value for.
1208
+ * @returns The value associated with the key.
1209
+ */ get(key) {
1608
1210
  this.initialize(false);
1609
- var _this_file_db_getSafe;
1610
- return (_this_file_db_getSafe = this.file.db.getSafe(this.prefix(key))) != null ? _this_file_db_getSafe : this.defaultValues[key];
1211
+ return this.db.getSafe(this.prefix(key)) ?? this.defaultValues[key];
1611
1212
  }
1612
- getAll() {
1213
+ /**
1214
+ * Gets all the values in the section.
1215
+ * @returns All the values in the section.
1216
+ */ getAll() {
1613
1217
  this.initialize(false);
1614
- var _this_file_db_getSafe;
1615
- return (_this_file_db_getSafe = this.file.db.getSafe(this.prefix())) != null ? _this_file_db_getSafe : this.defaultValues;
1218
+ return this.db.getSafe(this.prefix()) ?? JSON.parse(JSON.stringify(this.defaultValues));
1616
1219
  }
1617
- count() {
1618
- return Object.keys(this.file.db.getSafe(this.prefix()) || {}).length;
1220
+ /**
1221
+ * Gets the keys in the section.
1222
+ * @returns The keys in the section.
1223
+ */ get keys() {
1224
+ // if (this.keysAreFixed) return Object.keys(this.defaultValues)
1225
+ return Object.keys(this.getAll());
1226
+ }
1227
+ /**
1228
+ * Gets the number of keys in the section.
1229
+ * @returns The number of keys in the section.
1230
+ */ count() {
1231
+ return this.keys.length;
1619
1232
  }
1620
- set(key, value, save = true) {
1233
+ /**
1234
+ * Sets the value associated with the specified key.
1235
+ * @param key - The key to set the value for.
1236
+ * @param value - The value to set.
1237
+ * @param save - Indicates whether to save the section after setting the value.
1238
+ */ set(key, value, save = true) {
1621
1239
  this.initialize();
1622
1240
  this.assertValid(key, value);
1623
- this.file.db.set(this.prefix(key), value, save);
1241
+ this.db.set(this.prefix(key), value, save);
1624
1242
  }
1625
- setAll(values, save = true) {
1243
+ /**
1244
+ * Sets all the values in the section.
1245
+ * @param values - The values to set.
1246
+ * @param save - Indicates whether to save the section after setting the values.
1247
+ */ setAll(values, save = true) {
1626
1248
  const original = this.getAll();
1627
1249
  for (const [key, value] of Object.entries(values)){
1250
+ if (value === original[key]) continue;
1628
1251
  if (JSON.stringify(value) === JSON.stringify(original[key])) continue;
1629
1252
  this.set(key, value, false);
1630
1253
  }
1631
- if (!this.hasFixedKeysForUser) {
1254
+ if (!this.keysAreFixed) {
1632
1255
  for (const name of Object.keys(original)){
1633
1256
  if (Object.hasOwn(values, name)) continue;
1634
1257
  this.delete(name, false);
@@ -1636,122 +1259,206 @@ function inheritHiddenGlobalOptions(cmd) {
1636
1259
  }
1637
1260
  if (save) this.save();
1638
1261
  }
1639
- reset(key, save = true) {
1262
+ /**
1263
+ * Updates the value associated with the specified key.
1264
+ * @param key - The key to set the value for.
1265
+ * @param callback - The callback function that returns the new value.
1266
+ * @param save - Indicates whether to save the section after setting the value.
1267
+ */ update(key, callback, save = true) {
1268
+ this.set(key, callback(this.get(key), key), save);
1269
+ }
1270
+ /**
1271
+ * Resets the value associated with the specified key to its default value.
1272
+ * @param key - The key to reset.
1273
+ * @param save - Indicates whether to save the section after resetting the value.
1274
+ */ reset(key, save = true) {
1640
1275
  this.set(key, this.defaultValues[key], save);
1641
1276
  }
1642
- resetAll(save = true) {
1277
+ /**
1278
+ * Resets all the values in the section to their default values.
1279
+ * @param save - Indicates whether to save the section after resetting the values.
1280
+ */ resetAll(save = true) {
1643
1281
  this.setAll(this.defaultValues, save);
1644
1282
  }
1645
1283
  /**
1646
- * Actions:
1647
- * - save the database to disk
1648
- * - verify that the JSON file is valid JSON data
1649
- * - format as human readable with 2 indents
1650
- */ save() {
1651
- this.file.db.save();
1652
- }
1653
- delete(key, save = true) {
1284
+ * Deletes the value associated with the specified key.
1285
+ * @param key - The key to delete.
1286
+ * @param save - Indicates whether to save the section after deleting the value.
1287
+ */ delete(key, save = true) {
1654
1288
  this.initialize();
1655
- this.file.db.delete(this.prefix(key), save);
1289
+ this.db.delete(this.prefix(key), save);
1656
1290
  }
1657
- deleteAll(save = true) {
1658
- for (const key of Object.keys(this.getAll())){
1291
+ /**
1292
+ * Deletes all the values in the section.
1293
+ * @param save - Indicates whether to save the section after deleting the values.
1294
+ */ deleteAll(save = true) {
1295
+ for (const key of this.keys){
1659
1296
  this.delete(key, false);
1660
1297
  }
1661
1298
  if (save) this.save();
1662
1299
  }
1663
- edit(editor) {
1300
+ /**
1301
+ * Edits the values in the section using a text editor.
1302
+ * @param editor - The text editor to use. If not specified, the default text editor command will be used.
1303
+ */ edit(editor) {
1664
1304
  const original = this.getAll();
1665
1305
  const parsed = promptUserEditJsonInTextEditorSync(original, editor || defaultOpenInEditorCommand());
1666
1306
  this.setAll(parsed);
1667
1307
  }
1308
+ }
1309
+
1310
+ /**
1311
+ * A class that represents the appdata section of the JSON file used as simple database.
1312
+ */ class AppDataSection extends AbstractJsonFileSection {
1668
1313
  /**
1669
- * @param file The parent JsonFile instance.
1670
- * @param name The name of the section.
1671
- */ constructor(file, name, hasFixedKeysForUser){
1672
- super();
1673
- this.file = file;
1674
- this.name = name;
1675
- this.hasFixedKeysForUser = hasFixedKeysForUser;
1314
+ * Creates an instance of AppDataSection.
1315
+ * @param file - The parent JsonFile instance.
1316
+ * @param name - The name of the section.
1317
+ */ constructor(file, name){
1318
+ super(file, name, false);
1319
+ }
1320
+ /**
1321
+ * Does nothing
1322
+ */ assertValid() {
1323
+ return;
1324
+ }
1325
+ /**
1326
+ * Defines a property for the section.
1327
+ * @param key - The key of the property.
1328
+ */ defineProperty(key, value) {
1329
+ if (typeof value === 'object') value = JSON.parse(JSON.stringify(value));
1330
+ this.defaultValues[key] = value;
1676
1331
  this.isInitialized = false;
1677
- this.isInitializing = false;
1678
- this._defaultValues = {};
1679
1332
  }
1333
+ /**
1334
+ * Initializes the section.
1335
+ * @param save - Indicates whether to save the section after initialization.
1336
+ * @returns A string if an error occurred during initialization, otherwise void.
1337
+ */ initialize(save = false) {
1338
+ if (this.isInitialized) return;
1339
+ const data = this.db.getSafe(this.prefix());
1340
+ if (!data) this.db.set(this.prefix(), this.defaultValues, save);
1341
+ this.isInitialized = true;
1342
+ }
1343
+ }
1344
+
1345
+ /**
1346
+ * Creates a function that merges objects based on a predicate function.
1347
+ */ function createObjectMerger(predicate) {
1348
+ return function objMerge(target, ...sources) {
1349
+ for (const src of sources){
1350
+ for (const [key, value] of Object.entries(src)){
1351
+ if (predicate(value, key, src)) {
1352
+ target[key] = value;
1353
+ }
1354
+ }
1355
+ }
1356
+ return target;
1357
+ };
1680
1358
  }
1681
1359
 
1360
+ const objAssign = createObjectMerger((value)=>value != null);
1361
+
1682
1362
  /**
1683
1363
  * A class that represents the user-config section of the JSON file used as simple database.
1684
1364
  */ class ConfigSection extends AbstractJsonFileSection {
1685
- assertValid(key, value) {
1365
+ /**
1366
+ * Creates an instance of AbstractJsonFileSection.
1367
+ * @param file - The parent JsonFile instance.
1368
+ * @param name - The name of the section.
1369
+ */ constructor(file, name){
1370
+ super(file, name, true);
1371
+ /**
1372
+ * String parsers for when editing config from command-line.
1373
+ */ this.parsers = {};
1374
+ /**
1375
+ * Descriptions for each property key.
1376
+ */ this.descriptions = {};
1377
+ /**
1378
+ * Validators for each property key.
1379
+ */ this.validators = {};
1380
+ }
1381
+ /**
1382
+ * Asserts that a key-value pair is valid.
1383
+ * @param key - The key to assert.
1384
+ * @param value - The value to assert.
1385
+ */ assertValid(key, value) {
1686
1386
  if (!this.validators[key]) return;
1687
- assertThat(value, this.validators[key]);
1387
+ ensureThat(value, this.validators[key]);
1688
1388
  }
1689
- defineProperty(key, options) {
1389
+ /**
1390
+ * Defines a property for the section.
1391
+ * @param key - The key of the property.
1392
+ * @param options - The options for the property.
1393
+ */ defineProperty(key, options) {
1690
1394
  const { description, defaultValue, parse, validate } = options;
1691
- this.defaultValues[key] = defaultValue;
1692
- this.parsers[key] = parse != null ? parse : parseString;
1693
- this.validators[key] = validate != null ? validate : isString;
1694
- this.descriptions[key] = description != null ? description : '';
1395
+ this.descriptions[key] = description;
1396
+ this.defaultValues[key] = typeof defaultValue === 'object' ? JSON.parse(JSON.stringify(defaultValue)) : defaultValue;
1397
+ if (parse) this.parsers[key] = parse;
1398
+ if (validate) this.validators[key] = validate;
1695
1399
  this.assertValid(key, options.defaultValue);
1696
1400
  }
1697
- initialize(save = true) {
1401
+ /**
1402
+ * Initializes the section.
1403
+ * @param save - Indicates whether to save the section after initialization.
1404
+ * @returns A string if an error occurred during initialization, otherwise void.
1405
+ */ initialize(save = false) {
1698
1406
  if (this.isInitialized) return;
1699
- if (!this.file.cmd.features.isConfigEnabled) {
1700
- throw new JsonFileError('ConfigSection.initialize() called when config is disabled');
1701
- }
1702
- var _this_file_db_getSafe;
1703
- const values = (_this_file_db_getSafe = this.file.db.getSafe(this.prefix())) != null ? _this_file_db_getSafe : this.defaultValues;
1704
- const config = {};
1705
- for (const key of Object.keys(this.defaultValues)){
1706
- if (values[key] != null) {
1707
- config[key] = values[key];
1708
- } else {
1709
- config[key] = this.defaultValues[key];
1710
- }
1711
- }
1712
- this.file.db.set(this.prefix(), config, save);
1407
+ const data = this.db.getSafe(this.prefix());
1408
+ const result = objAssign({}, JSON.parse(JSON.stringify(this.defaultValues)), data || {});
1409
+ this.db.set(this.prefix(), result, save);
1713
1410
  this.isInitialized = true;
1714
1411
  }
1715
- get keys() {
1716
- return [
1717
- ...Object.keys(this.defaultValues)
1718
- ];
1719
- }
1720
- constructor(file, name, hasFixedKeysForUser = true){
1721
- super(file, name, hasFixedKeysForUser);
1722
- this.descriptions = {};
1723
- this.validators = {};
1724
- this.parsers = {};
1725
- // if (file.cmd.isRoot) {
1726
- // this.defineProperty('editor', {
1727
- // description: 'application launch command for your preferred text editor.',
1728
- // defaultValue: defaultOpenInEditorCommand() as Val,
1729
- // parse: parseString,
1730
- // })
1731
- // }
1732
- if (file.cmd.features.isPresetsEnabled) {
1733
- this.defineProperty('disabledBuiltinPresets', {
1734
- description: 'Builtin presets that are disabled.',
1735
- defaultValue: [],
1736
- parse: createTypedListParser(',', parseString),
1737
- validate: function isArrayOfPresetNames(value) {
1738
- if (!Array.isArray(value)) return false;
1739
- return value.every((name)=>{
1740
- return Object.hasOwn(file.presets.defaultValues, name);
1741
- });
1742
- }
1743
- });
1744
- }
1745
- }
1746
1412
  }
1747
1413
 
1748
- class JsonDB extends Base {
1749
- get filepath() {
1750
- return getJsonFilepath(this.file.cmd);
1414
+ /**
1415
+ * Reads a JSON file and then parses it into an object.
1416
+ * If an error occurs, it returns undefined.
1417
+ *
1418
+ * @param filepath - The path to the JSON file.
1419
+ * @param options - Options for reading the JSON file.
1420
+ */ function readJsonFileSafeSync(filepath, options) {
1421
+ try {
1422
+ return fs.readJsonSync(filepath, options);
1423
+ } catch (error) {
1424
+ return undefined;
1751
1425
  }
1752
- set(prefix, value = {}, save = true) {
1426
+ }
1427
+
1428
+ /**
1429
+ * Represents a simple JSON file database.
1430
+ */ class JsonDB {
1431
+ /**
1432
+ * Creates a new instance of the JsonDB class.
1433
+ * @param filepath - The filepath where the data file is stored or to be stored.
1434
+ */ constructor(filepath, indents = 0){
1435
+ this.filepath = filepath;
1436
+ this.indents = indents;
1437
+ this.data = readJsonFileSafeSync(this.filepath) ?? {};
1438
+ }
1439
+ /**
1440
+ * Saves the data to the JSON file.
1441
+ */ async save(indents = this.indents) {
1442
+ return await outputJson(this.filepath, this.data, {
1443
+ spaces: indents
1444
+ });
1445
+ }
1446
+ /**
1447
+ * Sets the filepath of the JSON file.
1448
+ * @param filepath - The new filepath.
1449
+ */ setFilepath(filepath, save = true) {
1450
+ if (this.filepath === filepath) return;
1451
+ this.filepath = filepath;
1452
+ if (save) this.save();
1453
+ }
1454
+ /**
1455
+ * Sets a value in the JSON database.
1456
+ * @param prefix - Object path prefix as dot-separated keys.
1457
+ * @param value - The value to set.
1458
+ * @param save - Whether to save the data to the JSON file.
1459
+ */ set(prefix, value = {}, save = true) {
1753
1460
  if (!prefix) {
1754
- this.data = assertThat(value, isObject);
1461
+ this.data = ensureThat(value, isObject);
1755
1462
  } else {
1756
1463
  const keys = prefix.split('.');
1757
1464
  const lastKey = keys.pop();
@@ -1766,12 +1473,21 @@ class JsonDB extends Base {
1766
1473
  }
1767
1474
  if (save) this.save();
1768
1475
  }
1769
- get(prefix) {
1476
+ /**
1477
+ * Gets a value from the JSON database.
1478
+ * @param prefix - Object path prefix as dot-separated keys.
1479
+ * @returns The value associated with the key.
1480
+ * @throws An error if no entry is found at the specified key.
1481
+ */ get(prefix) {
1770
1482
  const value = this.getSafe(prefix);
1771
- if (value === undefined) throw new Error(`No config entry with key '${prefix}'`);
1772
- return value;
1483
+ if (value === undefined) throw new Error(`No entry at '${prefix}'`);
1484
+ return this.cloneDeep(value);
1773
1485
  }
1774
- getSafe(prefix) {
1486
+ /**
1487
+ * Gets a value from the JSON database safely.
1488
+ * @param prefix - Object path prefix as dot-separated keys.
1489
+ * @returns The value associated with the key, or undefined if no entry is found.
1490
+ */ getSafe(prefix) {
1775
1491
  if (!prefix) return this.cloneDeep(this.data);
1776
1492
  const keys = prefix.split('.');
1777
1493
  let node = this.data;
@@ -1783,11 +1499,19 @@ class JsonDB extends Base {
1783
1499
  }
1784
1500
  return this.cloneDeep(node);
1785
1501
  }
1786
- has(prefix) {
1502
+ /**
1503
+ * Checks if a key exists in the JSON database.
1504
+ * @param prefix - Object path prefix as dot-separated keys.
1505
+ * @returns True if the key exists, false otherwise.
1506
+ */ has(prefix) {
1787
1507
  if (!prefix) return true;
1788
1508
  return this.getSafe(prefix) !== undefined;
1789
1509
  }
1790
- delete(prefix, save = true) {
1510
+ /**
1511
+ * Deletes a value from the JSON database.
1512
+ * @param prefix - Object path prefix as dot-separated keys.
1513
+ * @param save - Whether to save the data to the JSON file.
1514
+ */ delete(prefix, save = true) {
1791
1515
  if (!prefix) {
1792
1516
  this.data = {};
1793
1517
  } else {
@@ -1804,160 +1528,110 @@ class JsonDB extends Base {
1804
1528
  }
1805
1529
  if (save) this.save();
1806
1530
  }
1807
- cloneDeep(obj) {
1808
- return JSON.parse(JSON.stringify(obj));
1809
- }
1810
1531
  /**
1811
- * @param filepath The path to the JSON file.
1812
- */ constructor(file){
1813
- super();
1814
- this.file = file;
1815
- var _readJsonFileSafeSync;
1816
- this.data = (_readJsonFileSafeSync = readJsonFileSafeSync(this.filepath)) != null ? _readJsonFileSafeSync : {};
1817
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1818
- const [queue, writeJsonFileSafeLimited] = funAsyncRateLimit(writeJsonFileSafe, {
1819
- concurrency: 1,
1820
- autoStart: true,
1821
- timeout: 3000,
1822
- throwOnTimeout: true
1823
- });
1824
- const opts = {
1825
- spaces: 2
1826
- };
1827
- this.save = ()=>{
1828
- writeJsonFileSafeLimited(this.filepath, this.data, opts).catch((err)=>{
1829
- var _err;
1830
- this.file.cmd.outputUserError(((_err = err) == null ? void 0 : _err.message) || String(err));
1831
- });
1832
- };
1833
- // const cmdr = getCommander(this.file.cmd)
1834
- // const awaitQueue = async () => await queue.onIdle()
1835
- // process.on('exit', awaitQueue)
1836
- // cmdr.exitOverride(awaitQueue)
1532
+ * Creates a deep clone of an object.
1533
+ * @param obj - The object to clone.
1534
+ * @returns The cloned object.
1535
+ */ cloneDeep(obj) {
1536
+ if (typeof obj !== 'object') return obj;
1537
+ return JSON.parse(JSON.stringify(obj));
1837
1538
  }
1838
1539
  }
1839
1540
 
1840
- function assertPresetArgsOptional(cmd, args) {
1841
- args.forEach((arg, i)=>{
1842
- if (arg != null && i < cmd.arguments.length && cmd.arguments[i].required) {
1843
- throw new Error(`Cannot preset required arguments.`);
1844
- }
1845
- });
1846
- }
1847
-
1848
- const isArray = Array.isArray;
1849
-
1850
- /**
1851
- * Determine whether the input is a string array.
1852
- */ function isStringArray(value) {
1853
- return Array.isArray(value) && value.every((v)=>isString(v));
1854
- }
1855
-
1856
- function isStringWithNoSpacesOrDashes(value) {
1857
- return isString(value) && /^[^\s-]+$/i.test(value);
1858
- }
1859
-
1860
- function assertValidPreset(cmd, key, preset) {
1861
- const { description, presets, args, options } = preset;
1862
- assertThat(key, isStringWithNoSpacesOrDashes);
1863
- assertThat(description, isString);
1864
- assertThat(presets, isStringArray);
1865
- assertThat(args, isArray);
1866
- assertPresetArgsOptional(cmd, args);
1867
- assertValidArguments(cmd, args);
1868
- assertThat(options, isObject);
1869
- assertValidOptions(cmd, options);
1870
- }
1871
-
1872
- function createObjectMerger(predicate) {
1873
- return function objMerge(target, ...sources) {
1874
- for (const src of sources){
1875
- for (const [key, value] of Object.entries(src)){
1876
- if (predicate(value, key, src)) {
1877
- target[key] = value;
1878
- }
1879
- }
1880
- }
1881
- return target;
1882
- };
1883
- }
1884
-
1885
- const objAssign = createObjectMerger((value)=>value != null);
1886
-
1887
1541
  /**
1888
1542
  * A class that represents the user-presets section of the JSON file used as simple database.
1889
1543
  */ class PresetsSection extends AbstractJsonFileSection {
1890
- assertValid(key, value) {
1891
- assertValidPreset(this.file.cmd, key, value);
1544
+ /**
1545
+ * Creates an instance of AppDataSection.
1546
+ * @param file - The parent JsonFile instance.
1547
+ * @param name - The name of the section.
1548
+ */ constructor(file, name){
1549
+ super(file, name, false);
1550
+ this.defineProperty('defaults', {
1551
+ description: 'All presets inherit from this preset',
1552
+ presets: [],
1553
+ args: this.cmd.arguments.map((arg)=>arg.defaultValue ?? null),
1554
+ options: this.cmd.getOwnAndGlobalOptions().reduce((acc, opt)=>{
1555
+ acc[opt.attributeName()] = opt.defaultValue ?? (opt.isBoolean() ? false : null);
1556
+ return acc;
1557
+ }, {})
1558
+ });
1559
+ }
1560
+ /**
1561
+ * Asserts that a key-value pair is valid.
1562
+ * @param key - The key to assert.
1563
+ * @param value - The preset options to validate.
1564
+ */ assertValid(key, value) {
1565
+ this.cmd.assertValidPreset(key, value);
1892
1566
  }
1893
- defineProperty(key, options) {
1894
- this.defaultValues[key] = options;
1567
+ /**
1568
+ * Defines a property for the section.
1569
+ * @param key - The key of the property.
1570
+ * @param options - The options for the property.
1571
+ */ defineProperty(key, options) {
1572
+ if (this.defaultValues[key]) throw new Error(`Cannot redefine preset '${key}'.`);
1573
+ this.defaultValues[key] = JSON.parse(JSON.stringify(options));
1895
1574
  this.assertValid(key, options);
1896
1575
  this.isInitialized = false;
1897
1576
  }
1898
- initialize(save = true) {
1577
+ /**
1578
+ * Initializes the section.
1579
+ * @param save - Indicates whether to save the section after initialization.
1580
+ * @returns A string if an error occurred during initialization, otherwise void.
1581
+ */ initialize(save = false) {
1899
1582
  if (this.isInitialized) return;
1900
- const data = this.file.db.getSafe(this.prefix());
1901
- const hasData = data && Object.keys(data).length;
1902
- const presets = objAssign({}, JSON.parse(JSON.stringify(this.defaultValues)), data || {});
1903
- // remove disabled presets
1904
- const disabled = this.file.db.getSafe('config.disabledBuiltinPresets');
1905
- for (const name of disabled || []){
1906
- if (Object.hasOwn(presets, name)) {
1907
- delete presets[name];
1908
- save = true;
1909
- }
1910
- }
1911
- // merge data
1912
- if (hasData) {
1913
- // presets['defaults'].args = arrAssign([], this.defaultValues['defaults'].args, presets['defaults'].args)
1914
- // presets['defaults'].options = objAssign({}, this.defaultValues['defaults'].options, presets['defaults'].options)
1915
- // for (const preset of Object.values(presets)) {
1916
- // preset.args = arrAssign([], this.defaultValues['defaults'].args, preset.args)
1917
- // preset.options = objAssign({}, this.defaultValues['defaults'].options, preset.options)
1918
- // preset.presets = preset.presets || this.defaultValues['defaults'].presets
1919
- // // objAssign(preset.options, this.defaultValues['defaults'].options)
1920
- // // preset.options = objFilter(preset.options, (_, key) => Object.hasOwn(this.defaultValues, key))
1921
- // // preset.presets = preset.presets.filter((key: string) => Object.hasOwn(this.defaultValues, key))
1922
- // }
1923
- if (JSON.stringify(data) !== JSON.stringify(presets)) {
1924
- save = true;
1583
+ const data = this.db.getSafe(this.prefix());
1584
+ const presets = objAssign({}, this.defaultValues, data || {});
1585
+ const presetNames = Object.keys(presets);
1586
+ for (const preset of Object.values(presets)){
1587
+ for (const pname of presetNames){
1588
+ if (preset.options[pname]) {
1589
+ preset.presets.push(pname);
1590
+ delete preset.options[pname];
1591
+ save = true;
1592
+ }
1925
1593
  }
1926
1594
  }
1927
- this.file.db.set(this.prefix(), presets, save);
1595
+ this.db.set(this.prefix(), presets, save);
1928
1596
  this.isInitialized = true;
1929
1597
  }
1930
- delete(name, save = true) {
1931
- if (name === 'defaults') throw new JsonFileError('Cannot delete the "defaults" preset.');
1932
- super.delete(name, save);
1933
- if (!Object.hasOwn(this.defaultValues, name)) return;
1934
- const disabled = this.file.config.get('disabledBuiltinPresets');
1935
- if (disabled.includes(name)) return;
1936
- this.file.config.set('disabledBuiltinPresets', disabled.concat(name), save);
1937
- }
1938
- async setAll(presets, save = true) {
1939
- if (!presets['defaults']) throw new JsonFileError('Missing "defaults" preset');
1598
+ /**
1599
+ * Sets all the values in the section.
1600
+ * @param values - The values to set.
1601
+ * @param save - Indicates whether to save the section after setting the values.
1602
+ */ async setAll(presets, save = true) {
1603
+ if (!presets['defaults']) presets['defaults'] = JSON.parse(JSON.stringify(this.defaultValues['defaults']));
1940
1604
  super.setAll(presets, save);
1941
1605
  }
1942
- constructor(file, name, hasFixedKeysForUser = false){
1943
- super(file, name, hasFixedKeysForUser);
1944
- var _arg_defaultValue;
1945
- this.defineProperty('defaults', {
1946
- description: 'All presets inherit from this preset',
1947
- presets: [],
1948
- args: this.file.cmd.arguments.map((arg)=>(_arg_defaultValue = arg.defaultValue) != null ? _arg_defaultValue : null),
1949
- options: getOwnAndGlobalOptions(this.file.cmd).reduce((acc, opt)=>{
1950
- var _opt_defaultValue;
1951
- acc[opt.attributeName()] = (_opt_defaultValue = opt.defaultValue) != null ? _opt_defaultValue : opt.isBoolean() ? false : null;
1952
- return acc;
1953
- }, {})
1954
- });
1606
+ /**
1607
+ * Deletes the value associated with the specified key.
1608
+ * @param key - The key to delete.
1609
+ * @param save - Indicates whether to save the section after deleting the value.
1610
+ */ delete(name, save = true) {
1611
+ if (Object.hasOwn(this.defaultValues, name)) throw new Error('Cannot delete the builtin presets.');
1612
+ super.delete(name, save);
1955
1613
  }
1956
1614
  }
1957
1615
 
1958
1616
  /**
1959
1617
  * A class that represents the JSON file used as a simple database.
1960
- */ class JsonFile extends Base {
1618
+ */ class JsonFile {
1619
+ /**
1620
+ * @param cmd The parent CommandBuilder instance.
1621
+ */ constructor(cmd){
1622
+ this.cmd = cmd;
1623
+ }
1624
+ /**
1625
+ * A lazy-loaded instance of the JsonDB instance containing the data.
1626
+ * Upon first property access, it is stored as a property on the instance.
1627
+ * If the command is a subcommand, the root command's JsonDB instance is returned.
1628
+ */ get db() {
1629
+ if (this.cmd.isRoot) {
1630
+ return realizeLazyProperty(this, 'db', new JsonDB(this.cmd.dataFilepath, 2));
1631
+ } else {
1632
+ return this.cmd.root.db.db;
1633
+ }
1634
+ }
1961
1635
  /**
1962
1636
  * A lazy-loaded instance of the `config` section of the JSON file.
1963
1637
  * Upon first property access, it is stored as a property on the instance.
@@ -1965,30 +1639,22 @@ const objAssign = createObjectMerger((value)=>value != null);
1965
1639
  return realizeLazyProperty(this, 'config', new ConfigSection(this, 'config'));
1966
1640
  }
1967
1641
  /**
1642
+ * A lazy-loaded instance of the `appData` section of the JSON file.
1643
+ * Upon first property access, it is stored as a property on the instance.
1644
+ */ get appData() {
1645
+ return realizeLazyProperty(this, 'appData', new AppDataSection(this, 'appData'));
1646
+ }
1647
+ /**
1968
1648
  * A lazy-loaded instance of the `presets` section of the JSON file.
1969
1649
  */ get presets() {
1970
1650
  return realizeLazyProperty(this, 'presets', new PresetsSection(this, 'presets'));
1971
1651
  }
1972
- /**
1973
- * @param cmd The parent CommandBuilder instance.
1974
- */ constructor(cmd){
1975
- super();
1976
- this.cmd = cmd;
1977
- this.db = new JsonDB(this);
1978
- }
1979
1652
  }
1980
1653
 
1981
- function objDestroy(obj) {
1982
- for (const key of Reflect.ownKeys(obj)){
1983
- Reflect.deleteProperty(obj, key);
1654
+ class OptionArgumentParserSelector extends ParserSelector {
1655
+ constructor(builder){
1656
+ super(builder);
1984
1657
  }
1985
- }
1986
-
1987
- function optHasArgument(opt) {
1988
- return /[<>[\]]/.test(opt.flags);
1989
- }
1990
-
1991
- class OptionArgumentParserSelector extends AbstractStringParserSelector {
1992
1658
  custom(parser) {
1993
1659
  const name = this.builder.$.attributeName();
1994
1660
  this.builder.cmd.meta.optParsers[name] = parser;
@@ -1996,7 +1662,10 @@ class OptionArgumentParserSelector extends AbstractStringParserSelector {
1996
1662
  }
1997
1663
  }
1998
1664
 
1999
- class OptionArgumentValidatorSelector extends AbstractValidatorSelector {
1665
+ class OptionArgumentValidatorSelector extends ValidatorSelector {
1666
+ constructor(builder){
1667
+ super(builder);
1668
+ }
2000
1669
  custom(validator) {
2001
1670
  const name = this.builder.$.attributeName();
2002
1671
  const obj = this.builder.cmd.meta.optValidators;
@@ -2006,7 +1675,54 @@ class OptionArgumentValidatorSelector extends AbstractValidatorSelector {
2006
1675
  }
2007
1676
  }
2008
1677
 
2009
- class OptionReader extends Base {
1678
+ /**
1679
+ * Extract the argument name from an option's 'flags' string.
1680
+ */ function getArgumentName(opt) {
1681
+ const result = arrLast(opt.flags.trim().split(' '));
1682
+ if (/-/.test(result)) return undefined;
1683
+ return result;
1684
+ }
1685
+ /**
1686
+ * Check if an option has an argument.
1687
+ */ function hasArgument(opt) {
1688
+ return /[<>[\]]/.test(opt.flags);
1689
+ }
1690
+ /**
1691
+ * Update an Option's 'flags' property from its 'short' and 'long' properties.
1692
+ * The flags property is not automatically updated when 'short' or 'long' are changed.
1693
+ */ function renderFlags(opt) {
1694
+ const shortLong = [];
1695
+ if (opt.short) shortLong.push(opt.short);
1696
+ if (opt.long) shortLong.push(opt.long);
1697
+ const argName = getArgumentName(opt);
1698
+ return shortLong.join(', ') + (argName ? ' ' + argName : '');
1699
+ }
1700
+ /**
1701
+ * Set an Option's 'long' name. The 'flags' property is updated accordingly.
1702
+ * The '--' prefix is automatically added if not present.
1703
+ */ function setLong(opt, long) {
1704
+ opt.long = strEnsureStartsWith(long, '--').replace(/^-+/, '--');
1705
+ opt.flags = renderFlags(opt);
1706
+ }
1707
+ /**
1708
+ * Set an Option's 'short' name. The 'flags' property is updated accordingly.
1709
+ * The '-' prefix is automatically added if not present.
1710
+ */ function setShort(opt, short) {
1711
+ opt.short = strEnsureStartsWith(short, '-').replace(/^-+/, '-');
1712
+ opt.flags = renderFlags(opt);
1713
+ }
1714
+ const OptionHelpers = {
1715
+ getArgumentName,
1716
+ hasArgument,
1717
+ renderFlags,
1718
+ setLong,
1719
+ setShort
1720
+ };
1721
+
1722
+ class OptionReader {
1723
+ constructor(parent){
1724
+ this.parent = parent;
1725
+ }
2010
1726
  get $() {
2011
1727
  return this.parent.$;
2012
1728
  }
@@ -2019,9 +1735,6 @@ class OptionReader extends Base {
2019
1735
  get optional() {
2020
1736
  return this.$.optional;
2021
1737
  }
2022
- // get negate() {
2023
- // return this.$.negate
2024
- // }
2025
1738
  get mandatory() {
2026
1739
  return this.$.mandatory;
2027
1740
  }
@@ -2037,9 +1750,9 @@ class OptionReader extends Base {
2037
1750
  get long() {
2038
1751
  return this.$.long;
2039
1752
  }
2040
- // get preset() {
2041
- // return this.$.presetArg
2042
- // }
1753
+ get preset() {
1754
+ return this.$.presetArg;
1755
+ }
2043
1756
  get default() {
2044
1757
  return this.$.defaultValue;
2045
1758
  }
@@ -2058,37 +1771,33 @@ class OptionReader extends Base {
2058
1771
  get attributeName() {
2059
1772
  return this.$.attributeName();
2060
1773
  }
2061
- get fullDescription() {
2062
- return this.$.fullDescription();
2063
- }
2064
1774
  get defaultValueDescription() {
2065
1775
  return this.$.defaultValueDescription;
2066
1776
  }
2067
1777
  get hasArgument() {
2068
- return optHasArgument(this.parent.$);
2069
- }
2070
- constructor(parent){
2071
- super();
2072
- this.parent = parent;
1778
+ return OptionHelpers.hasArgument(this.parent.$);
2073
1779
  }
2074
1780
  }
2075
1781
 
2076
1782
  /**
2077
1783
  * Wrapper around the @see Option class, for more intuitive construction.
2078
1784
  * @remarks Options are one of boolean, negated, required argument, or optional argument.
2079
- */ class OptionBuilder extends Base {
1785
+ */ class OptionBuilder {
1786
+ constructor(cmd, flags){
1787
+ this.cmd = cmd;
1788
+ this.$ = new Option(flags);
1789
+ if (!OptionHelpers.hasArgument(this.$) && this.$.long?.startsWith('--no-')) {
1790
+ this.$.default(true);
1791
+ }
1792
+ }
2080
1793
  description(string) {
2081
1794
  this.$.description = string;
2082
1795
  return this;
2083
1796
  }
2084
- // negate(negate = true) {
2085
- // this.$.negate = negate
2086
- // return this
2087
- // }
2088
- // mandatory(mandatory = true) {
2089
- // this.$.makeOptionMandatory(mandatory)
2090
- // return this
2091
- // }
1797
+ mandatory(mandatory = true) {
1798
+ this.$.makeOptionMandatory(mandatory);
1799
+ return this;
1800
+ }
2092
1801
  hideHelp(hide = true) {
2093
1802
  this.$.hideHelp(hide);
2094
1803
  return this;
@@ -2097,21 +1806,18 @@ class OptionReader extends Base {
2097
1806
  this.$.hidden = hidden;
2098
1807
  return this;
2099
1808
  }
2100
- // preset(arg: unknown) {
2101
- // this.$.preset(arg)
2102
- // return this
2103
- // }
1809
+ preset(arg) {
1810
+ this.$.preset(arg);
1811
+ return this;
1812
+ }
2104
1813
  default(value, description) {
2105
- if (!optHasArgument(this.$)) {
2106
- throw new Error('Cannot set default value on option without argument: ' + this.$.name());
2107
- }
2108
- if (!this.$.optional) {
2109
- throw new Error('Cannot set default value on required option: ' + this.$.name());
2110
- }
2111
1814
  this.$.default(value, description);
2112
1815
  return this;
2113
1816
  }
2114
1817
  choices(values) {
1818
+ if (!OptionHelpers.hasArgument(this.$)) {
1819
+ throw new Error('Cannot set choices on option with no argument: ' + this.$.name());
1820
+ }
2115
1821
  this.$.choices(values);
2116
1822
  return this;
2117
1823
  }
@@ -2128,116 +1834,334 @@ class OptionReader extends Base {
2128
1834
  return this;
2129
1835
  }
2130
1836
  short(short) {
2131
- setOptionShortName(this.$, short);
1837
+ OptionHelpers.setShort(this.$, short);
2132
1838
  return this;
2133
1839
  }
2134
1840
  get parser() {
2135
- if (this.$.isBoolean()) {
2136
- throw new Error('Cannot set parser on boolean option: ' + this.$.attributeName());
1841
+ if (!OptionHelpers.hasArgument(this.$)) {
1842
+ throw new Error('Cannot set parser on option with no argument: ' + this.$.attributeName());
2137
1843
  }
2138
1844
  return new OptionArgumentParserSelector(this);
2139
1845
  }
2140
1846
  get validator() {
2141
- if (this.$.isBoolean()) {
2142
- throw new Error('Cannot set validator on boolean option: ' + this.$.attributeName());
1847
+ if (!OptionHelpers.hasArgument(this.$)) {
1848
+ throw new Error('Cannot set validator on option with no argument: ' + this.$.attributeName());
2143
1849
  }
2144
1850
  return new OptionArgumentValidatorSelector(this);
2145
1851
  }
2146
1852
  get get() {
2147
1853
  return realizeLazyProperty(this, 'get', new OptionReader(this));
2148
1854
  }
2149
- constructor(cmd, flags){
2150
- var _this_$_long;
2151
- super();
2152
- this.cmd = cmd;
2153
- this.$ = new Option(flags);
2154
- if (this.$.isBoolean() && ((_this_$_long = this.$.long) == null ? void 0 : _this_$_long.startsWith('--no-'))) {
2155
- this.$.default(true);
1855
+ }
1856
+
1857
+ /**
1858
+ * Updates the property descriptors of the specified properties on the given object.
1859
+ * @param object - The object whose property descriptors are to be updated.
1860
+ * @param properties - An array of property names for which the descriptors are to be updated.
1861
+ * @param update - A function that takes a property descriptor and a property name, and returns a new property descriptor.
1862
+ * @throws Will throw an error if any of the specified properties do not exist on the object.
1863
+ * @example ```ts
1864
+ * const obj = { a: 1, b: 2 };
1865
+ * objUpdatePropertyDescriptors(obj, ['a', 'b'], (descriptor, property) => {
1866
+ * descriptor.writable = true;
1867
+ * return obj;
1868
+ * });
1869
+ * ```
1870
+ */ function objUpdatePropertyDescriptors(object, properties, update) {
1871
+ for (const p of properties){
1872
+ if (!Reflect.has(object, p)) {
1873
+ throw new Error(`Property, '${p}' does not exist on object.`);
2156
1874
  }
1875
+ const descriptor = update(Object.getOwnPropertyDescriptor(object, p), p);
1876
+ Object.defineProperty(object, p, descriptor);
2157
1877
  }
2158
1878
  }
2159
1879
 
1880
+ /**
1881
+ * Sets the specified properties of an object as non-enumerable.
1882
+ * @remarks This function modifies the original object by setting the specified properties as non-enumerable.
1883
+ * If the object or any of the property names are not valid, it throws an error.
1884
+ * @param object The object whose properties are to be set as non-enumerable.
1885
+ * @param properties The names of the properties to be set as non-enumerable.
1886
+ * @throws Will throw an error if any of the specified properties do not exist on the object.
1887
+ * @example ```ts
1888
+ * setNonEnumerable({ a: 1, b: 2, c: 3 }, 'a', 'b');
1889
+ * Object.keys({ a: 1, b: 2, c: 3 });;
1890
+ * //=> ['c']
1891
+ * ```
1892
+ */ function setNonEnumerable(object, ...properties) {
1893
+ objUpdatePropertyDescriptors(object, properties, (descriptor)=>{
1894
+ descriptor.enumerable = false;
1895
+ return descriptor;
1896
+ });
1897
+ }
1898
+
2160
1899
  /**
2161
1900
  * Wrapper around the @see Command class, for more intuitive construction.
2162
- */ class CommandBuilder extends Base {
1901
+ */ class CommandBuilder {
1902
+ static{
1903
+ this.dataDirectory = path.join(os.homedir(), 'config', 'cli');
1904
+ }
2163
1905
  get db() {
2164
1906
  return realizeLazyProperty(this, 'db', new JsonFile(this));
2165
1907
  }
2166
- get name() {
2167
- return this.$.name();
1908
+ constructor(name, callback, parent, isNative = false){
1909
+ this.features = new CommandFeatureSelector(this);
1910
+ this.parent = null;
1911
+ this.meta = new CommandBuilderMetaData();
1912
+ this.meta.isNative = isNative;
1913
+ this.$ = new Command(name);
1914
+ commanderBackRefs.set(this.$, this);
1915
+ if (parent) {
1916
+ this.parent = parent;
1917
+ this.parent.meta.subcommands.push(this);
1918
+ this.parent.$.addCommand(this.$);
1919
+ }
1920
+ this.initializeHelp();
1921
+ this.initializeActionWrapper();
1922
+ if (callback) callback.call(this, this);
1923
+ if (this.parent) {
1924
+ this.$.copyInheritedSettings(this.parent.$);
1925
+ this.features.inheritFrom(this.parent.features);
1926
+ this.inheritParentHiddenGlobals();
1927
+ }
1928
+ if (!this.meta.isNative) {
1929
+ this.assertCommandNameNotReserved(this.name);
1930
+ this.addUtilCommands();
1931
+ }
1932
+ if (this.features.isAutoAssignSubCommandAliasesEnabled) {
1933
+ this.assignSubCommandAliases();
1934
+ if (!this.meta.isNative) {
1935
+ this.assertNoDuplicateCommandNames();
1936
+ }
1937
+ }
1938
+ if (this.features.isAutoAssignMissingOptionFlagsEnabled) {
1939
+ this.assignMissingOptionFlags();
1940
+ if (!this.meta.isNative) {
1941
+ this.assertNoDuplicateOptionNames();
1942
+ }
1943
+ }
1944
+ this.meta.isInitialized = true;
1945
+ }
1946
+ setRecommended() {
1947
+ this.enableBuiltinOptions({
1948
+ debug: true,
1949
+ disableStderr: true,
1950
+ disableStdout: true
1951
+ });
1952
+ this.autoAssignMissingOptionFlags();
1953
+ this.autoAssignSubCommandAliases();
1954
+ this.presetsEnabled();
1955
+ }
1956
+ deleteDataFile() {
1957
+ const filepath = this.dataFilepath;
1958
+ if (fs.existsSync(filepath)) remove(filepath);
2168
1959
  }
2169
1960
  version(string) {
1961
+ this.assertNotInitialized();
2170
1962
  this.$.version(string);
1963
+ const opt = this.options.find((o)=>o.attributeName() === 'version');
1964
+ if (opt) this.meta.globalOptions.push(opt);
1965
+ return this;
1966
+ }
1967
+ description(...lines) {
1968
+ this.assertNotInitialized();
1969
+ const description = lines.join('\n');
1970
+ const summary = description.split(/(\. ?|\n|$)/)[0];
1971
+ this.$.summary(summary + '.');
1972
+ this.$.description(description);
2171
1973
  return this;
2172
1974
  }
2173
1975
  alias(alias) {
2174
- assertCommandNameNotReserved(alias);
1976
+ this.assertNotInitialized();
1977
+ this.assertCommandNameNotReserved(alias);
2175
1978
  this.$.alias(alias);
2176
1979
  return this;
2177
1980
  }
2178
1981
  aliases(...aliases) {
2179
- aliases.forEach((a)=>this.alias(a));
1982
+ this.assertNotInitialized();
1983
+ aliases.forEach((alias)=>this.assertCommandNameNotReserved(alias));
1984
+ this.$.aliases(aliases);
2180
1985
  return this;
2181
1986
  }
2182
- get isRoot() {
2183
- return !this.parent;
2184
- }
2185
- get arguments() {
2186
- return this.$.registeredArguments;
2187
- }
2188
- get options() {
2189
- return this.$.options;
2190
- }
2191
1987
  enableBuiltinOptions(options) {
2192
- if (!options || options.help) this.globalOption('-h, --help', 'show help');
1988
+ this.assertNotInitialized();
2193
1989
  if (!options || options.debug) this.globalOption('-D, --debug', 'Output debugging information.');
2194
1990
  if (!options || options.disableColor) this.globalOption('-C, --disable-color', 'Disable color in terminal output.');
2195
1991
  if (!options || options.disableStderr) this.globalOption('-E, --disable-stderr', 'Mute all output to stderr.');
2196
1992
  if (!options || options.disableStdout) this.globalOption('-O, --disable-stdout', 'Mute all output to stdout.');
1993
+ return this;
2197
1994
  }
2198
- outputHelp() {
2199
- this.$.help();
2200
- }
2201
- /**
2202
- * Display error message and exit (or call exitOverride).
2203
- */ outputUserError(message, options) {
2204
- this.$.error(message, options);
1995
+ argument(name, cb) {
1996
+ this.assertNotInitialized();
1997
+ const ins = new ArgumentBuilder(this, name);
1998
+ this.$.addArgument(ins.$);
1999
+ if (typeof cb === 'function') {
2000
+ cb(ins, this);
2001
+ } else if (typeof cb === 'string') {
2002
+ ins.description(cb);
2003
+ }
2004
+ return this;
2205
2005
  }
2206
- description(...lines) {
2207
- const description = lines.join('\n');
2208
- const summary = description.split(/(\. ?|\n|$)/)[0];
2209
- this.$.summary(summary + '.');
2210
- this.$.description(description);
2006
+ option(flags, cb) {
2007
+ this.assertNotInitialized();
2008
+ const ins = new OptionBuilder(this, flags);
2009
+ if (this.hasIdenticalParentOption(ins.$)) return this;
2010
+ this.$.addOption(ins.$);
2011
+ if (typeof cb === 'function') {
2012
+ cb(ins, this);
2013
+ } else if (typeof cb === 'string') {
2014
+ ins.description(cb);
2015
+ }
2211
2016
  return this;
2212
2017
  }
2213
- allowExcessArguments(bool = true) {
2214
- this.$.allowExcessArguments(bool);
2018
+ globalOption(flags, cb) {
2019
+ return this.option(flags, (ins)=>{
2020
+ const opt = ins.$;
2021
+ this.meta.globalOptions.push(opt);
2022
+ if (typeof cb === 'function') {
2023
+ cb(ins, this);
2024
+ } else if (typeof cb === 'string') {
2025
+ ins.description(cb);
2026
+ }
2027
+ if (opt.hidden) this.meta.hiddenGlobalOptions.add(opt);
2028
+ });
2029
+ }
2030
+ command(name, cb) {
2031
+ this.assertNotInitialized();
2032
+ new CommandBuilder(name, cb, this);
2215
2033
  return this;
2216
2034
  }
2217
- allowUnknownOption(bool = true) {
2218
- this.$.allowUnknownOption(bool);
2035
+ nativeCommand(name, cb) {
2036
+ this.assertNotInitialized();
2037
+ new CommandBuilder(name, cb, this, true);
2219
2038
  return this;
2220
2039
  }
2221
- /**
2222
- * Register callback to use as replacement for calling process.exit.
2223
- */ exitOverride(callback) {
2224
- this.$.exitOverride(callback);
2040
+ action(fn) {
2041
+ this.assertNotInitialized();
2042
+ Object.defineProperty(this.meta, 'actionHandler', {
2043
+ value: fn,
2044
+ configurable: true
2045
+ });
2225
2046
  return this;
2226
2047
  }
2227
- get root() {
2228
- if (this.isRoot) return this;
2229
- return getAncestors(this).pop();
2048
+ errorHandler(fn) {
2049
+ this.assertNotInitialized();
2050
+ Object.defineProperty(this.meta, 'errorHandler', {
2051
+ value: fn,
2052
+ configurable: true
2053
+ });
2054
+ return this;
2230
2055
  }
2231
- /**
2232
- * Display error message and exit (or call exitOverride).
2233
- */ outputDebugInfo(event, getProps = ()=>({})) {
2234
- OutputManager.getInstance().outputDebug(()=>_extends({
2235
- cmd: prefixString(this),
2236
- event
2237
- }, getProps()));
2056
+ appData(key, value) {
2057
+ this.assertNotInitialized();
2058
+ this.features.appData(true);
2059
+ this.db.appData.defineProperty(key, value);
2060
+ return this;
2238
2061
  }
2239
- hideGlobalOptions(...names) {
2240
- const globals = getGlobalOptions(this);
2062
+ config(key, entry) {
2063
+ this.assertNotInitialized();
2064
+ this.features.config(true);
2065
+ this.db.config.defineProperty(key, entry);
2066
+ return this;
2067
+ }
2068
+ preset(name, preset) {
2069
+ this.assertNotInitialized();
2070
+ this.features.presets();
2071
+ this.meta.presetOptionKeys.push(name);
2072
+ this.db.presets.defineProperty(name, {
2073
+ description: preset.description,
2074
+ presets: preset.presets ?? [],
2075
+ args: preset.args ?? [],
2076
+ options: preset.options ?? {}
2077
+ });
2078
+ return this;
2079
+ }
2080
+ presetsEnabled(boolean = true) {
2081
+ this.assertNotInitialized();
2082
+ this.features.presets(boolean);
2083
+ return this;
2084
+ }
2085
+ autoAssignMissingOptionFlags(boolean = true) {
2086
+ this.assertNotInitialized();
2087
+ this.features.autoAssignMissingOptionFlags(boolean);
2088
+ return this;
2089
+ }
2090
+ autoAssignSubCommandAliases(boolean = true) {
2091
+ this.assertNotInitialized();
2092
+ this.features.autoAssignSubCommandAliases(boolean);
2093
+ return this;
2094
+ }
2095
+ allowExcessArguments(bool = true) {
2096
+ this.assertNotInitialized();
2097
+ this.$.allowExcessArguments(bool);
2098
+ return this;
2099
+ }
2100
+ allowUnknownOption(bool = true) {
2101
+ this.assertNotInitialized();
2102
+ this.$.allowUnknownOption(bool);
2103
+ return this;
2104
+ }
2105
+ /**
2106
+ * Register callback to use as replacement for calling process.exit.
2107
+ */ exitOverride(callback) {
2108
+ this.assertNotInitialized();
2109
+ this.$.exitOverride(callback);
2110
+ return this;
2111
+ }
2112
+ throwInsteadOfProcessExit() {
2113
+ this.assertNotInitialized();
2114
+ const onErr = (err)=>{
2115
+ throw err;
2116
+ };
2117
+ this.exitOverride(onErr);
2118
+ this.errorHandler(onErr);
2119
+ }
2120
+ /**
2121
+ * Add hook for life cycle event.
2122
+ */ hook(event, listener) {
2123
+ this.assertNotInitialized();
2124
+ this.$.hook(event, listener);
2125
+ return this;
2126
+ }
2127
+ /**
2128
+ * You can customise the help by overriding Help properties using configureHelp(),
2129
+ * or with a subclass of Help by overriding createHelp().
2130
+ */ configureHelp(configuration) {
2131
+ this.assertNotInitialized();
2132
+ this.$.configureHelp(configuration);
2133
+ return this;
2134
+ }
2135
+ /**
2136
+ * Display the help or a custom message after an error occurs.
2137
+ */ showHelpAfterError(displayHelp) {
2138
+ this.assertNotInitialized();
2139
+ this.$.showHelpAfterError(displayHelp);
2140
+ return this;
2141
+ }
2142
+ /**
2143
+ * Display suggestion of similar commands for unknown commands, or options for unknown options.
2144
+ */ showSuggestionAfterError(displaySuggestion) {
2145
+ this.assertNotInitialized();
2146
+ this.$.showSuggestionAfterError(displaySuggestion);
2147
+ return this;
2148
+ }
2149
+ /**
2150
+ * Add additional text to be displayed with the built-in help.
2151
+ *
2152
+ * Position is 'before' or 'after' to affect just this command,
2153
+ * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
2154
+ */ addHelpText(position, text) {
2155
+ this.assertNotInitialized();
2156
+ this.$.addHelpText(position, text);
2157
+ return this;
2158
+ }
2159
+ throwCommanderError(message, exitCode = 1, type = 'error') {
2160
+ throw new CommanderError(exitCode, type, message);
2161
+ }
2162
+ hideGlobalOptions(...names) {
2163
+ this.assertNotInitialized();
2164
+ const globals = this.getGlobalOptions();
2241
2165
  names = names.length ? names : globals.map((opt)=>opt.attributeName());
2242
2166
  for (const name of names){
2243
2167
  if (!name) continue;
@@ -2249,12 +2173,13 @@ class OptionReader extends Base {
2249
2173
  break;
2250
2174
  }
2251
2175
  }
2252
- if (!found) throw new Error(`Unknown global option name: ${name} for command, ${this.name}`);
2176
+ if (!found) this.throwCommanderError(`Unknown global option name: ${name} for command, ${this.name}`);
2253
2177
  }
2254
2178
  return this;
2255
2179
  }
2256
2180
  unhideGlobalOptions(...names) {
2257
- const globals = getGlobalOptions(this);
2181
+ this.assertNotInitialized();
2182
+ const globals = this.getGlobalOptions();
2258
2183
  names = names.length ? names : globals.map((opt)=>opt.attributeName());
2259
2184
  for (const name of names){
2260
2185
  if (!name) continue;
@@ -2266,190 +2191,873 @@ class OptionReader extends Base {
2266
2191
  break;
2267
2192
  }
2268
2193
  }
2269
- if (!found) throw new Error(`Unknown global option name: ${name} for command, ${this.name}`);
2194
+ if (!found) this.throwCommanderError(`Unknown global option name: ${name} for command, ${this.name}`);
2270
2195
  }
2271
2196
  return this;
2272
2197
  }
2273
- argument(name, cb) {
2274
- const ins = new ArgumentBuilder(this, name);
2275
- this.$.addArgument(ins.$);
2276
- if (typeof cb === 'function') {
2277
- cb(ins, this);
2278
- } else if (typeof cb === 'string') {
2279
- ins.description(cb);
2280
- }
2281
- objDestroy(ins);
2198
+ /**
2199
+ * Set the directory for searching for executable subcommands of this command.
2200
+ */ executableDir(path) {
2201
+ this.assertNotInitialized();
2202
+ this.$.executableDir(path);
2282
2203
  return this;
2283
2204
  }
2284
- option(flags, cb) {
2285
- const ins = new OptionBuilder(this, flags);
2286
- this.$.addOption(ins.$);
2287
- if (typeof cb === 'function') {
2288
- cb(ins, this);
2289
- } else if (typeof cb === 'string') {
2290
- ins.description(cb);
2291
- }
2292
- objDestroy(ins);
2205
+ /**
2206
+ * Store option value.
2207
+ */ setOptionValue(key, value) {
2208
+ this.assertNotInitialized();
2209
+ this.$.setOptionValue(key, value);
2293
2210
  return this;
2294
2211
  }
2295
- globalOption(flags, cb) {
2296
- return this.option(flags, (ins)=>{
2297
- const opt = ins.get.option;
2298
- this.meta.globalOptions.push(opt);
2299
- if (typeof cb === 'function') {
2300
- cb(ins, this);
2301
- } else if (typeof cb === 'string') {
2302
- ins.description(cb);
2303
- }
2304
- if (opt.hidden) this.meta.hiddenGlobalOptions.add(opt);
2212
+ /**
2213
+ * Store option value and where the value came from.
2214
+ */ setOptionValueWithSource(key, value, source) {
2215
+ this.assertNotInitialized();
2216
+ this.$.setOptionValueWithSource(key, value, source);
2217
+ return this;
2218
+ }
2219
+ setDataFilepath(filepath) {
2220
+ this.assertNotInitialized();
2221
+ Object.defineProperty(this, 'dataFilepath', {
2222
+ value: filepath
2305
2223
  });
2224
+ if (Object.hasOwn(this, 'db') && Object.hasOwn(this.db, 'db')) {
2225
+ this.db.db.setFilepath(filepath);
2226
+ }
2306
2227
  }
2307
- command(name, cb) {
2308
- this.meta.subcommands.push(new CommandBuilder(name, cb, this));
2309
- return this;
2228
+ /**
2229
+ * Display error message and exit (or call exitOverride).
2230
+ */ outputError(message, options) {
2231
+ this.$.error(message, options);
2310
2232
  }
2311
- nativeCommand(name, cb) {
2312
- return this.command(name, (ins)=>{
2313
- ins.meta.isNative = true;
2314
- if (cb) cb(ins);
2233
+ /**
2234
+ * Output help information for this command.
2235
+ */ outputHelp() {
2236
+ console.log(this.getRenderedHelp());
2237
+ }
2238
+ /**
2239
+ * Display error message and exit (or call exitOverride).
2240
+ */ outputDebugMessage(event, getProps = ()=>({})) {
2241
+ OutputManager.getInstance().outputDebug(()=>({
2242
+ event,
2243
+ cmd: this.getPrefixString(),
2244
+ ...getProps()
2245
+ }));
2246
+ }
2247
+ parseArguments(args) {
2248
+ const last = this.arguments.length - 1;
2249
+ return args.map((arg, i)=>{
2250
+ if (!arg) return arg;
2251
+ const parse = this.meta.argParsers[i > last ? last : i];
2252
+ return parse ? Array.isArray(arg) ? arg.map(parse) : parse(arg) : arg;
2315
2253
  });
2316
2254
  }
2317
- action(fn) {
2318
- this.meta.actionHandler = fn;
2319
- return this;
2255
+ /**
2256
+ * Parses (and validates) options using the parsers defined in the command builder.
2257
+ */ parseOptions(opts) {
2258
+ for (const [key, value] of Object.entries(opts)){
2259
+ const parse = this.meta.optParsers[key];
2260
+ opts[key] = parse ? Array.isArray(value) ? value.map(parse) : parse(value) : value;
2261
+ }
2262
+ return opts;
2320
2263
  }
2321
- config(key, entry) {
2322
- this.features.config();
2323
- this.db.config.defineProperty(key, entry);
2324
- return this;
2264
+ /**
2265
+ * Validate ALREADY PARSED args using the validators defined in the command builder.
2266
+ */ assertValidArguments(parsedArgs) {
2267
+ const last = this.arguments.length - 1;
2268
+ parsedArgs.forEach((arg, i)=>{
2269
+ if (arg == null) return;
2270
+ const index = i > last ? last : i;
2271
+ const validators = this.meta.argValidators[index];
2272
+ if (!validators) return;
2273
+ for (const isValid of validators){
2274
+ ensureThat(arg, isValid, {
2275
+ Err: InvalidArgumentError
2276
+ });
2277
+ }
2278
+ });
2279
+ return parsedArgs;
2325
2280
  }
2326
- preset(name, preset) {
2327
- this.features.presets();
2328
- var _preset_presets, _preset_args, _preset_options;
2329
- this.db.presets.defineProperty(name, {
2330
- description: preset.description,
2331
- presets: (_preset_presets = preset.presets) != null ? _preset_presets : [],
2332
- args: (_preset_args = preset.args) != null ? _preset_args : [],
2333
- options: (_preset_options = preset.options) != null ? _preset_options : {}
2281
+ /**
2282
+ * Validate ALREADY PARSED options using the validators defined in the command builder.
2283
+ */ assertValidOptions(parsedOptions) {
2284
+ for (const [key, value] of Object.entries(parsedOptions)){
2285
+ if (!this.meta.optValidators[key]) continue;
2286
+ if (value == null) continue;
2287
+ for (const isValid of this.meta.optValidators[key]){
2288
+ ensureThat(value, isValid);
2289
+ }
2290
+ }
2291
+ return parsedOptions;
2292
+ }
2293
+ assertValidPreset(key, preset) {
2294
+ const { description, presets, args, options } = preset;
2295
+ ensureThat(key, isStringWithNoSpacesOrDashes);
2296
+ ensureThat(description, isString);
2297
+ ensureThat(presets, isStringArray);
2298
+ ensureThat(args, isArray);
2299
+ this.assertPresetArgsOptional(args);
2300
+ this.assertValidArguments(args);
2301
+ ensureThat(options, isObject);
2302
+ this.assertValidOptions(options);
2303
+ }
2304
+ get name() {
2305
+ return this.$.name();
2306
+ }
2307
+ /**
2308
+ * Get the command at the root of the command tree.
2309
+ */ get root() {
2310
+ if (this.isRoot) return this;
2311
+ return this.getAncestors().pop();
2312
+ }
2313
+ get isRoot() {
2314
+ return !this.parent;
2315
+ }
2316
+ get arguments() {
2317
+ return this.$.registeredArguments;
2318
+ }
2319
+ get options() {
2320
+ return this.$.options;
2321
+ }
2322
+ get commander() {
2323
+ return this.$;
2324
+ }
2325
+ get hasGrandChildren() {
2326
+ return this.meta.subcommands.some((sub)=>!!sub.meta.subcommands.length);
2327
+ }
2328
+ /**
2329
+ * Returns whether a command's last argument is variadic.
2330
+ */ get isLastArgVariadic() {
2331
+ if (!this.arguments.length) return false;
2332
+ return arrLast(this.arguments).variadic;
2333
+ }
2334
+ get dataFilepath() {
2335
+ return path.join(CommandBuilder.dataDirectory, this.root.name + '.json');
2336
+ }
2337
+ /**
2338
+ * Get the executable search directory.
2339
+ */ getExecutableDir() {
2340
+ return this.$.executableDir();
2341
+ }
2342
+ /**
2343
+ * Retrieve option value.
2344
+ */ getOptionValue(key) {
2345
+ return this.$.getOptionValue(key);
2346
+ }
2347
+ /**
2348
+ * Get source of option value.
2349
+ */ getOptionValueSource(key) {
2350
+ return this.$.getOptionValueSource(key);
2351
+ }
2352
+ /**
2353
+ * Get source of option value. See also .optsWithGlobals().
2354
+ */ getOptionValueSourceWithGlobals(key) {
2355
+ return this.$.getOptionValueSourceWithGlobals(key);
2356
+ }
2357
+ getActionHandler() {
2358
+ return this.meta.actionHandler;
2359
+ }
2360
+ getDescription() {
2361
+ return this.$.description();
2362
+ }
2363
+ getSummary() {
2364
+ return this.$.summary();
2365
+ }
2366
+ getVersion() {
2367
+ return this.$.version();
2368
+ }
2369
+ getAlias() {
2370
+ return this.$.alias();
2371
+ }
2372
+ getAliases() {
2373
+ return this.$.aliases();
2374
+ }
2375
+ /**
2376
+ * Get a commands prefix array based on all its parent/ancestor commands.
2377
+ */ getPrefixArray() {
2378
+ return this.getAncestors({
2379
+ includeSelf: true
2380
+ }).reverse().map((node)=>node.name);
2381
+ }
2382
+ /**
2383
+ * Get a commands prefix string based on all its parent/ancestor commands.
2384
+ */ getPrefixString() {
2385
+ return this.getPrefixArray().join(' ');
2386
+ }
2387
+ getGlobalOptions() {
2388
+ const result = [];
2389
+ for (const anc of this.getAncestors({
2390
+ includeSelf: true
2391
+ }).reverse()){
2392
+ for (const gopt of anc.meta.globalOptions){
2393
+ if (!this.meta.hiddenGlobalOptions.has(gopt)) {
2394
+ result.push(gopt);
2395
+ }
2396
+ }
2397
+ }
2398
+ return result;
2399
+ }
2400
+ getOwnAndGlobalOptions() {
2401
+ return this.options.concat(this.getGlobalOptions());
2402
+ }
2403
+ *getChildrenIterator(options) {
2404
+ if (options?.includeSelf) yield this;
2405
+ for (const sub of this.meta.subcommands){
2406
+ yield sub;
2407
+ yield* sub.getChildrenIterator();
2408
+ }
2409
+ }
2410
+ getChildren(options) {
2411
+ return [
2412
+ ...this.getChildrenIterator(options)
2413
+ ];
2414
+ }
2415
+ *getAncestorsIterator(options) {
2416
+ if (options?.includeSelf) yield this;
2417
+ let node = this.parent;
2418
+ while(node){
2419
+ yield node;
2420
+ node = node.parent;
2421
+ }
2422
+ }
2423
+ /**
2424
+ * Get a command's ancestors, optionally starting from the command itself.
2425
+ */ getAncestors(options) {
2426
+ return [
2427
+ ...this.getAncestorsIterator(options)
2428
+ ];
2429
+ }
2430
+ *getSiblingsIterator() {
2431
+ if (!this.parent) return;
2432
+ for (const sub of this.parent.meta.subcommands){
2433
+ if (sub === this) continue;
2434
+ yield sub;
2435
+ }
2436
+ }
2437
+ /**
2438
+ * Returns an array of sibling CommandBuilder objects.
2439
+ */ getSiblings() {
2440
+ return [
2441
+ ...this.getSiblingsIterator()
2442
+ ];
2443
+ }
2444
+ getClosestNonNativeParent() {
2445
+ for (const anc of this.getAncestorsIterator({
2446
+ includeSelf: true
2447
+ })){
2448
+ if (!anc.meta.isNative) return anc;
2449
+ }
2450
+ this.throwCommanderError('No non-native parent found');
2451
+ }
2452
+ getRenderedHelp() {
2453
+ return this.$.helpInformation();
2454
+ }
2455
+ getOptsWithGlobalsParsed() {
2456
+ return this.parseOptions(this.$.optsWithGlobals());
2457
+ }
2458
+ getParsedValidArgsOptsWithPresets() {
2459
+ const [presetArgs, presetOpts, presetOrder] = this.getPresetArgsAndOpts();
2460
+ const args = this.getParsedValidArgsWithPresets(presetArgs);
2461
+ const opts = this.getParsedValidOptsWithPresets(presetOpts);
2462
+ this.debugLogArgsOpts(args, opts, presetArgs, presetOpts, presetOrder);
2463
+ return [
2464
+ args,
2465
+ opts
2466
+ ];
2467
+ }
2468
+ getParsedValidArgsWithPresets(presetArgs) {
2469
+ const result = arrAssign([], ...presetArgs, this.parseArguments(this.$.args));
2470
+ this.combineVariadicArgs(result);
2471
+ this.assertValidArguments(result);
2472
+ return this.padArgsWithUndefinedUntilExpectedLength(result);
2473
+ }
2474
+ getParsedValidOptsWithPresets(presetOpts) {
2475
+ const parsed = this.getOptsWithGlobalsParsed();
2476
+ const opts = presetOpts.length ? objAssign({}, ...presetOpts, parsed) : parsed;
2477
+ this.deleteOptionsWithDefaultOrNoValue(opts);
2478
+ this.assertValidOptions(opts);
2479
+ return opts;
2480
+ }
2481
+ getPresetArgsAndOpts() {
2482
+ if (!this.features.isPresetsEnabled) return [
2483
+ [],
2484
+ [],
2485
+ []
2486
+ ];
2487
+ const presets = this.db.presets.getAll();
2488
+ const opts = this.$.optsWithGlobals();
2489
+ const selectedPresets = Object.keys(presets).filter((name)=>opts[name] === true);
2490
+ const presetOrder = Object.keys(opts).filter((key)=>selectedPresets.includes(key));
2491
+ const presetArgs = presetOrder.map((name)=>presets[name].args);
2492
+ const presetOpts = presetOrder.map((name)=>presets[name].options);
2493
+ return [
2494
+ presetArgs,
2495
+ presetOpts,
2496
+ presetOrder
2497
+ ];
2498
+ }
2499
+ combineVariadicArgs(result) {
2500
+ if (this.isLastArgVariadic && result.length && !Array.isArray(arrLast(result))) {
2501
+ const rest = result.splice(this.arguments.length - 1);
2502
+ result.push(rest.filter((arg)=>arg != null));
2503
+ }
2504
+ return result;
2505
+ }
2506
+ debugLogArgsOpts(args, opts, presetArgs, presetOpts, presetOrder) {
2507
+ if (opts['debug']) {
2508
+ if (this.features.isPresetsEnabled) {
2509
+ this.outputDebugMessage('parsePresets', ()=>({
2510
+ presetOrder,
2511
+ presetArgs,
2512
+ presetOpts
2513
+ }));
2514
+ }
2515
+ this.outputDebugMessage('parseArgsOpts', ()=>{
2516
+ return {
2517
+ args,
2518
+ opts,
2519
+ command: [
2520
+ this.root.name,
2521
+ ...this.meta.rawArgs
2522
+ ].join(' ')
2523
+ };
2524
+ });
2525
+ }
2526
+ }
2527
+ deleteOptionsWithDefaultOrNoValue(opts) {
2528
+ const names = new Set(this.getOwnAndGlobalOptions().map((o)=>o.attributeName()));
2529
+ for (const [key, value] of Object.entries(opts)){
2530
+ if (!names.has(key) || value === false || value == null) {
2531
+ setNonEnumerable(opts, key);
2532
+ }
2533
+ }
2534
+ for (const key of this.meta.presetOptionKeys){
2535
+ if (Object.hasOwn(opts, key)) {
2536
+ setNonEnumerable(opts, key);
2537
+ }
2538
+ }
2539
+ return opts;
2540
+ }
2541
+ handleOutputOptions() {
2542
+ const opts = this.$.optsWithGlobals();
2543
+ const om = OutputManager.getInstance().reset();
2544
+ if (opts['disableColor']) om.colors.enabled = false;
2545
+ if (opts['disableStderr']) om.stderr.disable();
2546
+ if (opts['disableStdout']) om.stdout.disable();
2547
+ if (opts['debug']) {
2548
+ om.debug.enable();
2549
+ om.drainDebugMessageQueue();
2550
+ }
2551
+ }
2552
+ padArgsWithUndefinedUntilExpectedLength(args) {
2553
+ while(args.length < this.arguments.length)args.push(undefined);
2554
+ return args;
2555
+ }
2556
+ assertPresetArgsOptional(args) {
2557
+ args.forEach((arg, i)=>{
2558
+ if (arg != null && i < this.arguments.length && this.arguments[i].required) {
2559
+ this.throwCommanderError(`Cannot preset required arguments.`);
2560
+ }
2334
2561
  });
2335
- return this;
2336
2562
  }
2337
- createMain() {
2338
- return async (argv = process.argv.slice(2))=>{
2339
- await this.$.parseAsync(getARGV(argv), {
2340
- from: 'user'
2563
+ addUtilCommands() {
2564
+ if (!this.hasGrandChildren && !this.features.isConfigEnabled && !this.features.isPresetsEnabled && !this.features.isAppDataEnabled) {
2565
+ return;
2566
+ }
2567
+ this.nativeCommand('util', (u)=>{
2568
+ const cmd = u.getClosestNonNativeParent();
2569
+ u.alias('u');
2570
+ u.description('Utility commands.');
2571
+ if (cmd.features.isConfigEnabled) {
2572
+ u.nativeCommand('config', createConfigCommand);
2573
+ }
2574
+ if (cmd.features.isPresetsEnabled && cmd.meta.hasCustomActionHandler) {
2575
+ u.nativeCommand('presets', createPresetsCommand);
2576
+ }
2577
+ if (cmd.hasGrandChildren) {
2578
+ u.nativeCommand('list', createUtilListCommand);
2579
+ }
2580
+ if (cmd.features.isConfigEnabled || cmd.features.isPresetsEnabled || cmd.features.isAppDataEnabled) {
2581
+ u.nativeCommand('filepath', createUtilFilepathCommand);
2582
+ }
2583
+ function createUtilFilepathCommand(f) {
2584
+ f.alias('f');
2585
+ f.description('Print filepath to JSON file containing user data, eg. config and presets.');
2586
+ f.action(async ()=>console.log(cmd.dataFilepath));
2587
+ }
2588
+ function createUtilListCommand(l) {
2589
+ l.alias('l');
2590
+ l.description('List nested subcommands.');
2591
+ l.option('--all', 'Include utility commands.');
2592
+ l.action(async (opts)=>{
2593
+ const filter = opts.all ? undefined : (prefix)=>{
2594
+ return !/ (config|presets|util)( .+)?$/gi.test(prefix);
2595
+ };
2596
+ const table = [];
2597
+ for (const c of cmd.getChildrenIterator({
2598
+ includeSelf: true
2599
+ })){
2600
+ const prefix = c.getPrefixString();
2601
+ if (filter && !filter(prefix)) continue;
2602
+ table.push([
2603
+ prefix,
2604
+ c.getSummary()
2605
+ ]);
2606
+ }
2607
+ const ansi = table.map((row)=>{
2608
+ const arr = row[0].split(' ');
2609
+ const last = arr.pop();
2610
+ let col = colors.magenta;
2611
+ if (row[1].startsWith('[Preset]')) {
2612
+ col = colors.green;
2613
+ } else if (/ (util|config|presets) /.test(row[0])) {
2614
+ col = colors.gray;
2615
+ } else if (/ (util|config|presets)/.test(row[0])) {
2616
+ col = colors.dim;
2617
+ }
2618
+ row[0] = arr.map(colors.dim).concat(col(last)).join(' ');
2619
+ return row;
2620
+ });
2621
+ console.log(formatTableForTerminal(ansi, [
2622
+ 'Command',
2623
+ 'Summary'
2624
+ ]));
2625
+ });
2626
+ }
2627
+ function createPresetsCommand(p) {
2628
+ const db = cmd.db.presets;
2629
+ p.alias('p');
2630
+ p.description('Edit presets in your text editor', '', 'A preset consists of pre-set arguments and/or options for a command.', 'Additionally, a preset can have other presets as dependencies.', 'When running the command, multiple presets can be stacked.', 'Required arguments cannot be pre-set.');
2631
+ p.nativeCommand('edit', (e)=>{
2632
+ e.alias('e');
2633
+ e.description('Edit as JSON in a text editor.');
2634
+ e.option('--editor [cmd]', 'The command to launch your preferred text editor.');
2635
+ e.action(async (opts)=>{
2636
+ db.edit(opts.editor);
2637
+ console.info(db.getAll());
2638
+ });
2639
+ });
2640
+ p.nativeCommand('list', (l)=>{
2641
+ l.alias('l');
2642
+ l.description('List all presets.');
2643
+ l.action(async ()=>console.dir(db.getAll(), {
2644
+ depth: null
2645
+ }));
2646
+ });
2647
+ for (const [key, preset] of Object.entries(db.getAll())){
2648
+ if (key === 'defaults') continue;
2649
+ cmd.option(`--${key}`, (o)=>{
2650
+ o.description('[Preset]: ' + preset.description);
2651
+ const implied = {
2652
+ defaults: true
2653
+ };
2654
+ const recurse = (preset)=>{
2655
+ if (implied[preset]) return;
2656
+ implied[preset] = true;
2657
+ db.get(preset).presets.forEach((k)=>recurse(k));
2658
+ };
2659
+ recurse(key);
2660
+ o.implies(implied);
2661
+ });
2662
+ }
2663
+ }
2664
+ function createConfigCommand(c) {
2665
+ const db = cmd.db.config;
2666
+ c.alias('c');
2667
+ c.description('Manage configuration file.');
2668
+ c.nativeCommand('edit', (e)=>{
2669
+ e.alias('e');
2670
+ e.description('Edit as JSON in a text editor.');
2671
+ e.option('--editor [cmd]', 'The command to launch your preferred text editor.');
2672
+ e.action(async (opts)=>{
2673
+ db.edit(opts.editor);
2674
+ console.info(db.getAll());
2675
+ });
2676
+ });
2677
+ c.nativeCommand('list', (l)=>{
2678
+ l.alias('l');
2679
+ l.description('Print entire config with details.');
2680
+ l.action(async ()=>{
2681
+ const result = db.keys.map((key)=>({
2682
+ key,
2683
+ description: db.descriptions[key],
2684
+ value: db.get(key),
2685
+ defaultValue: db.defaultValues
2686
+ }));
2687
+ console.dir(result, {
2688
+ depth: null
2689
+ });
2690
+ });
2691
+ });
2692
+ c.nativeCommand('get', (g)=>{
2693
+ g.alias('g');
2694
+ g.description('Print value(s) from the config.');
2695
+ g.argument('[key]', 'The key to print the value of. Omit to print all values.');
2696
+ g.action(async (key)=>console.log(key ? db.get(key) : db.getAll()));
2697
+ });
2698
+ c.nativeCommand('set', (s)=>{
2699
+ s.alias('s');
2700
+ s.description('Set a value in the config.');
2701
+ s.argument('<key>', 'The key to set the value of.');
2702
+ s.argument('<value>', 'The new value.');
2703
+ s.action(async (key, val)=>{
2704
+ const parse = db.parsers[key];
2705
+ const value = typeof parse === 'function' ? parse(val) : val;
2706
+ db.set(key, value);
2707
+ console.info({
2708
+ [key]: value
2709
+ });
2710
+ });
2711
+ });
2712
+ c.nativeCommand('reset', (r)=>{
2713
+ r.alias('r');
2714
+ r.description('Reset to defaults.');
2715
+ r.argument('[key]', 'The key for which to reset the value. Omit to reset entire config.');
2716
+ r.action(async (key)=>{
2717
+ if (key) db.reset(key);
2718
+ else db.resetAll();
2719
+ console.info(db.getAll());
2720
+ });
2721
+ });
2722
+ /*
2723
+ config.option('--editor [cmd]', (o) => {
2724
+ o.description('The command to launch your preferred text editor.')
2725
+ })
2726
+ config.argument('[action]', (a) => {
2727
+ a.description('The action to perform.')
2728
+ a.choices(['edit', 'list', 'get', 'set', 'reset'])
2729
+ a.default('edit')
2730
+ })
2731
+ config.argument('[key]', (a) => {
2732
+ a.description('Property key (if applicable)')
2733
+ })
2734
+ config.argument('[value]', (a) => {
2735
+ a.description('Value to set (if applicable)')
2736
+ })
2737
+ config.action(
2738
+ async (action: string, key: string, value: string, opts: { editor: string }, config: CommandBuilder) => {
2739
+ const cmd = config.getClosestNonNativeParent()
2740
+ const cfg = cmd.db.config
2741
+ if (!action || action === 'edit') {
2742
+ cfg.edit(opts.editor)
2743
+ return console.info(cfg.getAll())
2744
+ } else if (action === 'list') {
2745
+ return console.dir(
2746
+ cfg.keys.map((key: string) => {
2747
+ return {
2748
+ key,
2749
+ description: cfg.descriptions[key],
2750
+ value: cfg.get(key),
2751
+ defaultValue: cfg.defaultValues,
2752
+ }
2753
+ })
2754
+ )
2755
+ } else if (action === 'get') {
2756
+ if (key) return console.log(cfg.get(key))
2757
+ else return console.log(cfg.getAll())
2758
+ } else if (action === 'set') {
2759
+ const from = cfg.get(key)
2760
+ const parse = cfg.parsers[key]
2761
+ const to = typeof parse === 'function' ? cfg.parsers[key](value) : value
2762
+ cfg.set(key, to)
2763
+ return console.info({ changed: key, from, to })
2764
+ } else if (action === 'reset') {
2765
+ if (key) cfg.reset(key)
2766
+ else cfg.resetAll()
2767
+ return console.info(cfg.getAll())
2768
+ }
2769
+ }
2770
+ )*/ }
2771
+ });
2772
+ }
2773
+ /**
2774
+ * Makes aliases for the command.
2775
+ * The idea is to be able to navigate the command tree by only typing the first letter(s) of the command names.
2776
+ *
2777
+ * Example: A command 'cola' would get these aliases: ['c', 'co', 'col'].
2778
+ * However, if there are namespace clashes with sibling subcommands that start with the same letter,
2779
+ * eg. like 'cola' and 'coal' where the first two letters clash, cola's aliases are reduced to only ['col'] and similarly for 'coal'.
2780
+ *
2781
+ * This method creates the aliases, ensuring there are no clashes with sublings, why it is important that the
2782
+ * entire command tree is built before invoking this method.
2783
+ */ assignSubCommandAliases() {
2784
+ if (this.getAlias() || this.name.length <= 1) return this;
2785
+ const sibAliases = this.getSiblings().map((sib)=>sib.getAliases()).flat();
2786
+ for(let i = 0; i < this.name.length - 1; i++){
2787
+ let cmdAlias = this.name.substring(0, i + 1);
2788
+ let isClash = arrSome(sibAliases, (sibAlias)=>{
2789
+ return cmdAlias === sibAlias;
2341
2790
  });
2342
- };
2791
+ if (isClash && i === 0) {
2792
+ cmdAlias = cmdAlias.charAt(0).toUpperCase();
2793
+ isClash = arrSome(sibAliases, (sibAlias)=>{
2794
+ return cmdAlias === sibAlias;
2795
+ });
2796
+ }
2797
+ if (isClash) continue;
2798
+ this.alias(cmdAlias);
2799
+ return this;
2800
+ }
2801
+ return this;
2343
2802
  }
2344
- constructor(name, callback, parent = null){
2345
- super();
2346
- this.$ = new Command(name);
2347
- this.parent = parent;
2348
- if (this.parent) this.parent.$.addCommand(this.$);
2349
- this.meta = new CommandBuilderMetaData();
2350
- this.get = new CommandReader(this);
2351
- this.features = new CommandFeatureSelector(this);
2352
- initializeCommand(this, callback);
2803
+ /**
2804
+ * Automatically set 'short' and 'long' names to options that don't have one assigned yet.
2805
+ *
2806
+ * First, it tries to assign a short name based on the first letter of the option's attribute name
2807
+ * Both lower and upper case are tried. If these is not available, the next letter of the option name is tried.
2808
+ *
2809
+ * If none of the letters of the option name are available, the option is skipped until all other
2810
+ * options have had letters from their names attempted assigned.
2811
+ * Those that remain are assigned the first available letter of the alphabet + 0-9.
2812
+ * If there are 64 options for the command and no more alphanumeric characters are available,
2813
+ * the option is not assigned a short name.
2814
+ */ assignMissingOptionFlags() {
2815
+ const taken = new Set();
2816
+ for (const anc of this.getAncestorsIterator({
2817
+ includeSelf: true
2818
+ })){
2819
+ anc.options.forEach((opt)=>{
2820
+ if (!opt.short) return;
2821
+ taken.add(opt.short.replace(/^-/g, ''));
2822
+ });
2823
+ }
2824
+ const failed = new Set();
2825
+ // assign letter from option name
2826
+ this.options.forEach((opt)=>{
2827
+ if (opt.short) return;
2828
+ const name = opt.attributeName();
2829
+ for(let c = 0; c < name.length; c++){
2830
+ let char = name.charAt(c).toLowerCase();
2831
+ if (taken.has(char)) {
2832
+ char = char.toUpperCase();
2833
+ if (taken.has(char)) continue;
2834
+ }
2835
+ OptionHelpers.setShort(opt, char);
2836
+ taken.add(char);
2837
+ return;
2838
+ }
2839
+ failed.add(opt);
2840
+ });
2841
+ // assign random alphanumeric character.
2842
+ const name = 'abcdefghijklmnopqrstuvwxyz1234567890';
2843
+ failed.forEach((opt)=>{
2844
+ for(let c = 0; c < name.length; c++){
2845
+ let char = name.charAt(c);
2846
+ if (taken.has(char)) {
2847
+ char = char.toUpperCase();
2848
+ if (taken.has(char)) continue;
2849
+ }
2850
+ OptionHelpers.setShort(opt, char);
2851
+ taken.add(char);
2852
+ return;
2853
+ }
2854
+ });
2353
2855
  }
2354
- }
2355
- (()=>{
2356
- CommandBuilder.dataDirectory = path.join(os.homedir(), 'config', 'cli');
2357
- })();
2358
- function CLI(name, callback) {
2359
- return new CommandBuilder(name, callback);
2360
- }
2361
- class CommandReader extends Base {
2362
- get action() {
2363
- return this.cmd.$.builder.meta.actionHandler;
2856
+ assertNoDuplicateCommandNames() {
2857
+ const names = this.$.commands.map((sub)=>sub.aliases().concat(sub.name())).flat();
2858
+ if (names.length !== new Set(names).size) {
2859
+ this.throwCommanderError(`Duplicate subcommand names/aliases found for command, ${this.name}: ${names.join(', ')}`);
2860
+ }
2364
2861
  }
2365
- get description() {
2366
- return this.cmd.$.description();
2862
+ hasIdenticalParentOption(option) {
2863
+ const flags = option.flags;
2864
+ for (const anc of this.getAncestorsIterator({
2865
+ includeSelf: true
2866
+ })){
2867
+ for (const opt of anc.$.options){
2868
+ if (flags === opt.flags) {
2869
+ return true;
2870
+ }
2871
+ }
2872
+ }
2873
+ return false;
2367
2874
  }
2368
- get summary() {
2369
- return this.cmd.$.summary();
2875
+ assertNoDuplicateOptionNames() {
2876
+ const throwErr = (cmd, opt, anc)=>{
2877
+ this.throwCommanderError(`Duplicate option names > cmd: ${cmd.name}, ${anc ? `anc: ${anc.name}, ` : ''}opt: ${opt}`);
2878
+ };
2879
+ const set = new Set();
2880
+ for (const opt of this.options){
2881
+ if (opt.name() === 'help') continue;
2882
+ if (opt.short) {
2883
+ if (set.has(opt.short)) throwErr(this, opt.short);
2884
+ set.add(opt.short);
2885
+ }
2886
+ if (opt.long) {
2887
+ if (set.has(opt.long)) throwErr(this, opt.long);
2888
+ set.add(opt.long);
2889
+ }
2890
+ if (opt.attributeName()) {
2891
+ if (set.has(opt.attributeName())) throwErr(this, opt.attributeName());
2892
+ set.add(opt.attributeName());
2893
+ }
2894
+ }
2895
+ for (const anc of this.getAncestorsIterator()){
2896
+ for (const opt of anc.$.options){
2897
+ if (opt.short && set.has(opt.short)) {
2898
+ if (opt.short !== 'V') continue;
2899
+ throwErr(this, opt.short, anc);
2900
+ }
2901
+ if (opt.long && set.has(opt.long)) {
2902
+ throwErr(this, opt.long, anc);
2903
+ }
2904
+ if (opt.attributeName() && set.has(opt.attributeName())) {
2905
+ throwErr(this, opt.attributeName(), anc);
2906
+ }
2907
+ }
2908
+ }
2370
2909
  }
2371
- get version() {
2372
- return this.cmd.$.version();
2910
+ initializeActionWrapper() {
2911
+ this.$.action(()=>{
2912
+ const isAsync = isAsyncFunction(this.meta.actionHandler) || /^\(.+\) ?=> ?tslib_1\.__awaiter\(/.test(this.meta.actionHandler.toString().trim());
2913
+ if (isAsync) {
2914
+ try {
2915
+ this.handleOutputOptions();
2916
+ const [args, opts] = this.getParsedValidArgsOptsWithPresets();
2917
+ if (opts['help']) return Promise.resolve(this.outputHelp());
2918
+ return Promise.resolve(this.meta.actionHandler.call(this, ...args, opts, this));
2919
+ } catch (error) {
2920
+ this.meta.errorHandler.call(this, error, this);
2921
+ return Promise.reject(error);
2922
+ }
2923
+ } else {
2924
+ try {
2925
+ this.handleOutputOptions();
2926
+ const [args, opts] = this.getParsedValidArgsOptsWithPresets();
2927
+ if (opts['help']) return this.outputHelp();
2928
+ this.meta.actionHandler.call(this, ...args, opts, this);
2929
+ } catch (error) {
2930
+ this.meta.errorHandler.call(this, error, this);
2931
+ }
2932
+ }
2933
+ });
2373
2934
  }
2374
- get alias() {
2375
- return this.cmd.$.alias();
2935
+ initializeHelp() {
2936
+ if (this.isRoot) this.globalOption('-h, --help', 'show help');
2937
+ this.$.addHelpCommand('?', 'show help');
2938
+ this.$.configureHelp(DefaultHelpConfig);
2376
2939
  }
2377
- get aliases() {
2378
- return this.cmd.$.aliases();
2940
+ inheritParentHiddenGlobals() {
2941
+ if (!this.parent) return;
2942
+ for (const opt of this.parent.meta.hiddenGlobalOptions){
2943
+ this.meta.hiddenGlobalOptions.add(opt);
2944
+ }
2379
2945
  }
2380
- get renderHelp() {
2381
- return this.cmd.$.helpInformation();
2946
+ assertCommandNameNotReserved(name) {
2947
+ if (this.isRoot) return;
2948
+ if (this.meta.isNative) return;
2949
+ if (name === 'u' || name === 'util') {
2950
+ this.throwCommanderError(`Name '${name}' is reserved and is not available as name or alias.`);
2951
+ }
2382
2952
  }
2383
- constructor(cmd){
2384
- super();
2385
- this.cmd = cmd;
2953
+ assertNotInitialized() {
2954
+ if (this.meta.isInitialized) this.throwCommanderError('Command already initialized: ' + this.name);
2386
2955
  }
2387
2956
  }
2957
+ process.argv = splitCombinedArgvShorts(process.argv.slice());
2388
2958
 
2389
- /**
2390
- * Check whether a command exists in the PATH.
2391
- * @param command The command to check for.
2392
- */ async function commandExists(command) {
2393
- return await lookpath(command) !== undefined;
2394
- }
2395
-
2396
- /**
2397
- * Get the absolute file path of given command or undefined it does not exist in the PATH.
2398
- * @param command The command to check for.
2399
- */ async function commandLocation(command) {
2400
- return await lookpath(command);
2401
- }
2402
-
2403
- function errorToString(error) {
2404
- const name = error instanceof Error ? error.name : 'Error';
2405
- const msg = error instanceof Error ? error.message : String(error);
2406
- return name + ': ' + msg;
2959
+ function CLI(name, callback) {
2960
+ return ()=>new CommandBuilder(name, callback).commander;
2407
2961
  }
2408
2962
 
2409
2963
  /**
2410
- * Escape a string to be used as a shell command argument.
2411
- * Wraps in double quotes only if necessary.
2412
- */ function escapeShellCommandArgument(command) {
2413
- if (!/(["'$`\\ ])/.test(command)) return command;
2414
- return '"' + command.replace(/(["'$`\\])/g, '\\$1') + '"';
2415
- } /*
2416
- console.log(escapeShellCommandArgument('21')) ///=> 21
2417
- console.log(escapeShellCommandArgument('noproblem')) ///=> noproblem
2418
- console.log(escapeShellCommandArgument('foo bar')) ///=> "foo bar"
2419
- console.log(escapeShellCommandArgument("'c:\\program files\\node'")) ///=> "\'c:\\program files\\node\'"
2420
- console.log(escapeShellCommandArgument('^(wow|hi)$')) ///=> "^(wow|hi)\$"
2421
- console.log(escapeShellCommandArgument('^(wow|hi)$')) ///=> "^(wow|hi)\$"
2422
- */
2423
-
2424
- function forEachChildRecursive(cmd, callback, options) {
2425
- var _options;
2426
- if (((_options = options) == null ? void 0 : _options.includeSelf) && callback(cmd)) return true;
2427
- for (const sub of cmd.meta.subcommands){
2428
- if (callback(sub) || forEachChildRecursive(sub, callback)) {
2429
- return true;
2964
+ * Reads a file and returns the file's contents.
2965
+ *
2966
+ * Identical to fs.readFileSync, except that:
2967
+ * - it uses utf8 encoding by default
2968
+ * - if operation fails, returns undefined instead of throwing
2969
+ *
2970
+ * @param filepath - The path to the file.
2971
+ * @param encoding - The encoding to use when reading the file.
2972
+ * @returns The file's contents or undefined if the file does not exist.
2973
+ */ function readFileSafeSync(filepath, encoding = 'utf8') {
2974
+ try {
2975
+ return fs.readFileSync(filepath, encoding);
2976
+ } catch (error) {
2977
+ return undefined;
2978
+ }
2979
+ }
2980
+
2981
+ function execInherit(command) {
2982
+ return new Promise((resolve, reject)=>{
2983
+ try {
2984
+ const buffer = execSync(command, {
2985
+ stdio: 'inherit'
2986
+ });
2987
+ const string = buffer && buffer.toString ? buffer.toString().trim() : '';
2988
+ resolve(string);
2989
+ } catch (error) {
2990
+ const oError = error instanceof Error ? error : new Error(String(error));
2991
+ reject(oError);
2430
2992
  }
2431
- }
2993
+ });
2432
2994
  }
2433
2995
 
2434
- function getChildren(cmd, options) {
2435
- return [
2436
- ...walkChildren(cmd, options)
2437
- ];
2996
+ /**
2997
+ * Checks if the given string is in lower case.
2998
+ * @param input The string to be checked.
2999
+ * @example ```ts
3000
+ * strIsLowerCase('hello');
3001
+ * //=> true
3002
+ * strIsLowerCase('Hello');
3003
+ * //=> false
3004
+ * ```
3005
+ */ function strIsLowerCase(input) {
3006
+ return input === input.toLowerCase();
2438
3007
  }
2439
3008
 
2440
3009
  /**
2441
- * Get the command at the root of the command tree.
2442
- */ function getRootCommand(cmd) {
2443
- if (cmd.isRoot) return cmd;
2444
- return getAncestors(cmd).pop();
3010
+ * Checks if the given string is in upper case.
3011
+ * @param input The string to be checked.
3012
+ * @example ```ts
3013
+ * strIsUpperCase('HELLO');;
3014
+ * //=> true
3015
+ * strIsUpperCase('HEllo');;
3016
+ * //=> false
3017
+ * ```
3018
+ */ function strIsUpperCase(input) {
3019
+ return input === input.toUpperCase();
2445
3020
  }
2446
3021
 
2447
3022
  /**
2448
- * Set an Option's 'long' name. The 'flags' property is updated accordingly.
2449
- * The '--' prefix is automatically added if not present.
2450
- */ function setOptionLongName(opt, long) {
2451
- opt.long = strEnsureStartsWith(long, '--').replace(/^-+/, '--');
2452
- opt.flags = renderOptionFlags(opt);
3023
+ * Returns an array of words in the string
3024
+ * @param word The camel case word to split.
3025
+ * @throws Throws an error if the input is not a string.
3026
+ * @param input input string
3027
+ * @example ```ts
3028
+ * strSplitCamelCase('someCamel10Case')
3029
+ * //=> ['some', 'Camel10', 'Case']
3030
+ * ```
3031
+ */ function strSplitCamelCase(word) {
3032
+ if (!word) return [];
3033
+ if (word.length <= 2) return [
3034
+ word
3035
+ ];
3036
+ const result = [];
3037
+ const lastCharIndex = word.length - 1;
3038
+ let lastSplitIndex = 0;
3039
+ let foundCamelCase = false;
3040
+ for(let i = 1; i < word.length; i++){
3041
+ if (isWordSplitIndex(word, i)) {
3042
+ const sub = word.substring(lastSplitIndex, i);
3043
+ if (sub) {
3044
+ result.push(sub);
3045
+ lastSplitIndex = i;
3046
+ foundCamelCase = true;
3047
+ }
3048
+ }
3049
+ if (foundCamelCase && i === lastCharIndex) {
3050
+ const sub = word.substring(lastSplitIndex);
3051
+ if (sub) result.push(sub);
3052
+ }
3053
+ }
3054
+ if (!foundCamelCase) result.push(word);
3055
+ return result;
3056
+ }
3057
+ const regInteger = /\d+/g;
3058
+ const regSpecial = /[^\w]+/g;
3059
+ function isWordSplitIndex(word, index) {
3060
+ return strIsLowerCase(word[index - 1]) && strIsUpperCase(word[index]) && !regInteger.test(word[index - 1]) && !regInteger.test(word[index]) && !regSpecial.test(word[index - 1]) && !regSpecial.test(word[index]);
2453
3061
  }
2454
3062
 
2455
3063
  /**
@@ -2482,33 +3090,6 @@ function getChildren(cmd, options) {
2482
3090
  };
2483
3091
  }
2484
3092
 
2485
- /**
2486
- * Creates a function that validates if the length of the input is equal to the specified length.
2487
- * The returned function accepts any value with a 'name' property and is named 'isLengthOf' concatenated with the specified length.
2488
- * @param length - The length to validate against.
2489
- * @throws if length is not an integer.
2490
- */ function createLengthValidator(length) {
2491
- assertThat(length, Number.isInteger);
2492
- return funSetName('isLengthOf' + length, function(input) {
2493
- return input.length === length;
2494
- });
2495
- }
2496
-
2497
- /**
2498
- * Creates a parser function that parses a delimited string into a list of typed values.
2499
- * The parsers array corresponds to the ordering of the expected input values.
2500
- *
2501
- * @param delimiter - The delimiter used to split the string into individual values.
2502
- * @param parsers - An array of functions used to parse each individual value in the string.
2503
- * @returns A function that takes a delimited string and returns an array of typed values.
2504
- * @template T - The type of the values in the list.
2505
- */ function createTupleListParser(delimiter, parsers) {
2506
- const isValidLength = createLengthValidator(parsers.length);
2507
- return function parseList(string) {
2508
- return assertThat(string.split(delimiter).map((str)=>str.trim()), isValidLength).map((str, i)=>parsers[i](str));
2509
- };
2510
- }
2511
-
2512
3093
  /**
2513
3094
  * Parses a string into a boolean.
2514
3095
  *
@@ -2535,24 +3116,4 @@ function isBoolean(value) {
2535
3116
  return typeof value === 'boolean';
2536
3117
  }
2537
3118
 
2538
- /**
2539
- * Determine whether the input is an integer array.
2540
- */ function isIntegerArray(value) {
2541
- return Array.isArray(value) && value.every((v)=>isInteger(v));
2542
- }
2543
-
2544
- function isNull(value) {
2545
- return value === null;
2546
- }
2547
-
2548
- /**
2549
- * Determine whether the input is a number array.
2550
- */ function isNumberArray(value) {
2551
- return Array.isArray(value) && value.every((v)=>isNumber(v));
2552
- }
2553
-
2554
- function isNumericString(string) {
2555
- return /^-?[0-9,.]+$/.test(string.trim());
2556
- }
2557
-
2558
- export { AbstractJsonFileSection, AbstractStringParserSelector, AbstractValidatorSelector, ArgumentBuilder, ArgumentParserSelector, ArgumentReader, ArgumentValidatorSelector, Base, CLI, CommandBuilder, CommandBuilderMetaData, CommandFeatureSelector, CommandReader, ConfigSection, ErrorParser, JsonDB, JsonFile, JsonFileError, MethodDisabler, OptionArgumentParserSelector, OptionArgumentValidatorSelector, OptionBuilder, OptionReader, OutputManager, PresetsSection, actionWrapper, addConfigCommands, addPresetsCommands, addUtilCommands, arrAssign, assertCommandNameNotReserved, assertNoDuplicateCommandNames, assertNoDuplicateOptionNames, assertPresetArgsOptional, assertValidArguments, assertValidOptions, assertValidPreset, autoAssignMissingOptionFlags, autoAssignSubCommandAliases, combineVariadicArgs, commandExists, commandLocation, configureHelp, createArrayMerger, createBooleanParser, createConfig, createLengthValidator, createObjectMerger, createPresets, createTupleListParser, createTypedArrayValidator, createTypedListParser, debugLogArgsOpts, deleteOptionsWithDefaultOrNoValue, ensureBackRefToCommandBuilder, errParseStack, errPrettyStack, errToObject, errorToString, escapeShellCommandArgument, forEachChildRecursive, formatTableForTerminal, getARGV, getAncestors, getChildren, getClosestNonNativeParent, getGlobalOptions, getJsonFilepath, getOptionArgumentName, getOwnAndGlobalOptions, getPresetArgsAndOpts, getRootCommand, getSiblings, handleError, handleOutputOptions, hasVariadicArguments, initializeCommand, isArray, isBoolean, isInteger, isIntegerArray, isNamedFunction, isNamedFunctionArray, isNull, isNumber, isNumberArray, isNumericString, isString, isStringArray, isStringWithNoSpacesOrDashes, objAssign, objDestroy, optHasArgument, optsWithGlobalsParsed, padArgsWithUndefinedUntilExpectedLength, parseArguments, parseBoolean, parseInteger, parseNumber, parseOptions, parseString, parsedValidArgsOptsWithPresets, parsedValidArgsWithPresets, parsedValidOptsWithPresets, prefixArray, prefixString, prefixStringsRecursive, realizeLazyProperty, renderOptionFlags, setOptionLongName, setOptionShortName, splitCombinedArgvShorts, walkAncestors, walkChildren, walkSiblings };
3119
+ export { AbstractJsonFileSection, AppDataSection, ArgumentBuilder, ArgumentParserSelector, ArgumentReader, ArgumentValidatorSelector, CLI, CommandBuilder, CommandBuilderMetaData, CommandFeatureSelector, ConfigSection, DefaultHelpConfig, ErrorParser, JsonDB, JsonFile, MethodDisabler, OptionArgumentParserSelector, OptionArgumentValidatorSelector, OptionBuilder, OptionHelpers, OptionReader, OutputManager, ParserSelector, PresetsSection, ValidatorSelector, arrAssign, arrLast, arrSome, commanderBackRefs, createArrayMerger, createBooleanParser, createObjectMerger, createTypedArrayValidator, createTypedListParser, defaultOpenInEditorCommand, ensureThat, errParseStack, errPrettyStack, errToObject, execInherit, formatTableForTerminal, funSetName, getTempDataPath, isArray, isBoolean, isFunction, isInteger, isNamedFunction, isNamedFunctionArray, isOSX, isObject, isPlainObject, isPrimitive, isString, isStringArray, isStringWithNoSpacesOrDashes, isValidNumber, isVsCodeInstalled, isWindows, objAssign, objUpdatePropertyDescriptors, parseBoolean, parseInteger, parseNumber, parseString, promptUserEditInTextEditorSync, promptUserEditJsonInTextEditorSync, readFileSafeSync, readFileSync, readJsonFileSafeSync, realizeLazyProperty, regexEscapeString, setNonEnumerable, splitCombinedArgvShorts, strEnsureStartsWith, strFirstCharToUpperCase, strIsLowerCase, strIsUpperCase, strSplitCamelCase, tempFileSync };