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