@cqse/commons 1.0.0-beta.7 → 1.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.
package/lib/index.mjs ADDED
@@ -0,0 +1,822 @@
1
+ import process from "node:process";
2
+ import { createHash } from "node:crypto";
3
+
4
+ //#region src/Exceptions.ts
5
+ /**
6
+ * Excepting signaling that there is functionality that is supposed
7
+ * to be implemented in case the exception is thrown.
8
+ *
9
+ * This is helpful for writing prototypes and not yet handling all possible cases.
10
+ */
11
+ var ImplementMeException = class extends Error {
12
+ constructor(implementMeFor) {
13
+ super("Implement me!");
14
+ this._implementMeFor = implementMeFor;
15
+ }
16
+ get message() {
17
+ if (this._implementMeFor) return `Implement me for: ${this._implementMeFor}`;
18
+ else return "Implement me!";
19
+ }
20
+ };
21
+ /**
22
+ * Exception signaling that the application is in an invalid state.
23
+ */
24
+ var IllegalStateException = class extends Error {};
25
+ /**
26
+ * Exception signaling that an invalid argument has been passed for a parameter.
27
+ */
28
+ var IllegalArgumentException = class extends Error {};
29
+ /**
30
+ * Exception signaling that a component is mis-configured.
31
+ */
32
+ var InvalidConfigurationException = class extends Error {};
33
+
34
+ //#endregion
35
+ //#region src/Contract.ts
36
+ /**
37
+ * Methods to express and check code contracts.
38
+ */
39
+ var Contract = class {
40
+ /**
41
+ * The given predicate `condition` has to be satisfied.
42
+ *
43
+ * @param condition Condition predicate (boolean value)
44
+ * @param message Message describing the condition.
45
+ */
46
+ static require(condition, message) {
47
+ if (!condition) throw new IllegalArgumentException(message);
48
+ }
49
+ /**
50
+ * Require that the given argument `obj` is defined.
51
+ *
52
+ * @param obj The object that has to be defined.
53
+ * @param message The message describing this requirement.
54
+ */
55
+ static requireDefined(obj, message) {
56
+ if (obj) return obj;
57
+ if (typeof obj === "number" || typeof obj === "boolean") return obj;
58
+ if (typeof obj === "string" || obj instanceof String) return obj;
59
+ if (message) throw new IllegalArgumentException(message);
60
+ else throw new IllegalArgumentException("Reference must be defined.");
61
+ }
62
+ /**
63
+ * Require that the given string is not empty.
64
+ *
65
+ * @param text The string to check.
66
+ * @param message The message that describes the requirement.
67
+ */
68
+ static requireNonEmpty(text, message) {
69
+ this.requireDefined(text);
70
+ if (text.length === 0) throw new IllegalArgumentException(message);
71
+ return text;
72
+ }
73
+ /**
74
+ * Require that the string adheres to a particular pattern.
75
+ *
76
+ * @param text The string to check.
77
+ * @param regexp The regular expression to satisfy.
78
+ * @param message The message describing the requirement.
79
+ */
80
+ static requireStringPattern(text, regexp, message) {
81
+ this.requireDefined(text);
82
+ this.requireDefined(regexp);
83
+ if (!text.match(regexp)) throw new IllegalArgumentException(message);
84
+ return text;
85
+ }
86
+ };
87
+
88
+ //#endregion
89
+ //#region src/Strings.ts
90
+ /**
91
+ * Remove the given prefix, if present, from the given string.
92
+ *
93
+ * @param prefix - The prefix to remove.
94
+ * @param removeFrom - The string to remove the prefix from.
95
+ *
96
+ * @returns a new string where the prefix is removed.
97
+ */
98
+ function removePrefix(prefix, removeFrom) {
99
+ if (removeFrom.startsWith(prefix)) return removeFrom.substring(prefix.length);
100
+ return removeFrom;
101
+ }
102
+
103
+ //#endregion
104
+ //#region src/ConfigParser.ts
105
+ /**
106
+ * Configuration parser module that handles command-line arguments, environment variables,
107
+ * and configuration files for TypeScript/JavaScript applications.
108
+ *
109
+ * @module ConfigParser
110
+ */
111
+ /** Parameter group for --version and --help */
112
+ const CONFIG_GROUP_APP = {
113
+ order: 999,
114
+ title: "Application"
115
+ };
116
+ function expectedArgumentCount(parameterDescriptor) {
117
+ if (parameterDescriptor.type === "int" || parameterDescriptor.type === "string") return 1;
118
+ else if (parameterDescriptor.type === "bool") return 0;
119
+ else if (parameterDescriptor.type === "string[]") return Number.POSITIVE_INFINITY;
120
+ else throw new InvalidConfigurationException(`Unsupported parameter type "${parameterDescriptor.type}" for parameter "${parameterDescriptor.longParameter}".`);
121
+ }
122
+ /**
123
+ * Converts a parameter name (with dashes) to a camelCase parameter ID.
124
+ *
125
+ * @param parameterName - The parameter name to convert (e.g., --my-param)
126
+ * @returns The camelCase parameter ID (e.g., myParam)
127
+ */
128
+ function parameterNameToParameterId(parameterName) {
129
+ return parameterName.replace(/^-+/, "").replace(/-([a-z])/g, (_, char) => char.toUpperCase());
130
+ }
131
+ /**
132
+ * Converts a long parameter name to an environment variable name.
133
+ *
134
+ * @param longParameter - The long parameter name (e.g., --my-param)
135
+ * @returns The environment variable name (e.g., MY_PARAM)
136
+ */
137
+ function longParameterToEnvironmentVariableName(longParameter) {
138
+ return longParameter.replace(/^--/, "").toUpperCase().replace(/-/g, "_");
139
+ }
140
+ /**
141
+ * Manages configuration parameters and their properties.
142
+ * Provides functionality to add, describe, and parse parameters.
143
+ */
144
+ var ConfigurationParameters = class ConfigurationParameters {
145
+ constructor() {
146
+ this.parameters = /* @__PURE__ */ new Map();
147
+ this.shortToLongParameterMap = /* @__PURE__ */ new Map();
148
+ this.sequentialParameters = [];
149
+ this.argumentChecks = [];
150
+ }
151
+ /**
152
+ * Adds a new parameter to the configuration.
153
+ *
154
+ * @param shortParameter - Optional short form of the parameter (e.g., -p)
155
+ * @param longParameter - Long form of the parameter (e.g., --port)
156
+ * @param type - Parameter's data type
157
+ * @param options - Additional parameter options including a help text and default value
158
+ */
159
+ addParameter(shortParameter, longParameter, type, options = {}) {
160
+ const parameterId = parameterNameToParameterId(longParameter);
161
+ const environmentVariableName = longParameterToEnvironmentVariableName(longParameter);
162
+ this.describeParameter(parameterId, {
163
+ parameterId,
164
+ longParameter,
165
+ type,
166
+ shortParameter,
167
+ help: options.help ?? "",
168
+ default: options.default,
169
+ internal: options.internal ?? false,
170
+ group: options.group,
171
+ environmentVariableName
172
+ });
173
+ }
174
+ /**
175
+ * Adds a new argument check function to the list of checks.
176
+ *
177
+ * @param check - A function that takes an options object as input and returns a string containing an
178
+ * error message if a validation fails, or undefined if the validation passes.
179
+ */
180
+ addArgumentCheck(check) {
181
+ this.argumentChecks.push(check);
182
+ }
183
+ /**
184
+ * Makes the given parameter mandatory.
185
+ */
186
+ addRequiredArgumentFor(parameterId) {
187
+ const parameter = this.lookupParameter(parameterId);
188
+ if (parameter === void 0) throw new InvalidConfigurationException(`Unknown parameter "${parameterId}".`);
189
+ if (parameter.default !== void 0) throw new InvalidConfigurationException(`Parameter "${parameterId}" is required, but has a default value.`);
190
+ this.argumentChecks.push((parsedArguments) => {
191
+ if (parsedArguments[parameterId] === void 0) return `Missing required argument for parameter "${parameter.longParameter}"\n\tParameter description: ${parameter.help}`;
192
+ });
193
+ }
194
+ /**
195
+ * Describes a parameter with its complete set of properties.
196
+ *
197
+ * @param parameterId - Internal identifier for the parameter
198
+ * @param descriptor - Complete parameter descriptor
199
+ * @throws Error if short parameter is invalid or already mapped
200
+ */
201
+ describeParameter(parameterId, descriptor) {
202
+ this.parameters.set(parameterId, descriptor);
203
+ if (descriptor.shortParameter) {
204
+ const shortParameterId = descriptor.shortParameter.replace(/^-/, "").trim().toLowerCase();
205
+ if (shortParameterId.length < 1 || shortParameterId.length > 3) throw new InvalidConfigurationException(`Short parameter "${shortParameterId}" is invalid.`);
206
+ const existingMapping = this.shortToLongParameterMap.get(shortParameterId);
207
+ if (existingMapping !== void 0 && existingMapping !== parameterId) throw new InvalidConfigurationException(`Short parameter "${shortParameterId}" is already mapped to parameter "${existingMapping}".`);
208
+ this.shortToLongParameterMap.set(shortParameterId, descriptor.parameterId);
209
+ }
210
+ if (!descriptor.longParameter.startsWith("--")) this.sequentialParameters.push(parameterId);
211
+ }
212
+ /**
213
+ * Looks up a parameter descriptor by its ID.
214
+ *
215
+ * @param parameterId - Parameter identifier to look up
216
+ * @returns Parameter descriptor if found, undefined otherwise
217
+ */
218
+ lookupParameter(parameterId) {
219
+ let result = this.parameters.get(parameterId);
220
+ if (result === void 0) {
221
+ const longParameterId = this.shortToLongParameterMap.get(parameterId);
222
+ if (longParameterId !== void 0) result = this.parameters.get(longParameterId);
223
+ }
224
+ return result;
225
+ }
226
+ /**
227
+ * Returns all registered parameter descriptors.
228
+ *
229
+ * @returns Array of all parameter descriptors
230
+ */
231
+ getParameters() {
232
+ return Array.from(this.parameters.values());
233
+ }
234
+ /**
235
+ * Retrieves the collection of functions used for argument validation.
236
+ */
237
+ getArgumentChecks() {
238
+ return this.argumentChecks;
239
+ }
240
+ /**
241
+ * Returns an array of parameter IDs that are configured as sequential parameters.
242
+ * That is, parameters that are given without a name but where their mapping
243
+ * to concrete parameters is defined by their position in the command line.
244
+ */
245
+ getSequentialParameters() {
246
+ return this.sequentialParameters;
247
+ }
248
+ /**
249
+ * @returns A new configuration object with the same parameters as this one.
250
+ */
251
+ clone() {
252
+ const clone = new ConfigurationParameters();
253
+ for (const parameter of this.parameters.values()) clone.describeParameter(parameter.parameterId, parameter);
254
+ for (const check of this.argumentChecks) clone.addArgumentCheck(check);
255
+ return clone;
256
+ }
257
+ /**
258
+ * Adds all parameters and checks from the given configuration to this one.
259
+ */
260
+ addAllOf(toAdd) {
261
+ for (const parameter of toAdd.getParameters()) this.describeParameter(parameter.parameterId, parameter);
262
+ for (const check of toAdd.getArgumentChecks()) this.addArgumentCheck(check);
263
+ }
264
+ };
265
+ /**
266
+ * Parses command line arguments according to parameter definitions.
267
+ *
268
+ * @param args - Array of command line arguments to parse
269
+ * @param parameters - Configuration parameters defining valid options
270
+ * @returns Parsed options object with parameter values
271
+ * @throws Error if parameter values are invalid or missing
272
+ */
273
+ function parseArguments(args, parameters) {
274
+ const result = {};
275
+ let activeParameter;
276
+ let activeArguments = [];
277
+ let awaitingArguments = 0;
278
+ function finishParameter() {
279
+ if (!activeParameter) return;
280
+ if (awaitingArguments < Number.POSITIVE_INFINITY && awaitingArguments > 0) throw new InvalidConfigurationException(`Missing value for parameter "${activeParameter.longParameter}".`);
281
+ if (activeParameter.type === "bool") {
282
+ result[activeParameter.parameterId] = true;
283
+ activeParameter = void 0;
284
+ } else if (activeParameter.type === "string[]") {
285
+ const targetList = result[activeParameter.parameterId];
286
+ const argumentsNormalizedAsList = parseStringListArgument(activeArguments.join(","));
287
+ if (isDefinedStringList(targetList)) result[activeParameter.parameterId] = targetList.concat(argumentsNormalizedAsList);
288
+ else result[activeParameter.parameterId] = argumentsNormalizedAsList;
289
+ } else {
290
+ if (activeArguments.length !== 1) throw new InvalidConfigurationException(`Invalid value for parameter "${activeParameter.longParameter}": Expected a single value, got ${activeArguments.length}.`);
291
+ result[activeParameter.parameterId] = parseWithProperType(activeArguments[0], activeParameter);
292
+ }
293
+ activeParameter = void 0;
294
+ activeArguments = [];
295
+ }
296
+ for (const argument of args) {
297
+ if (argument.startsWith("-")) {
298
+ finishParameter();
299
+ const parameterId = parameterNameToParameterId(argument);
300
+ activeParameter = parameters.lookupParameter(parameterId);
301
+ if (!activeParameter) throw new InvalidConfigurationException(`Unknown configuration parameter: ${argument}`);
302
+ awaitingArguments = expectedArgumentCount(activeParameter);
303
+ } else {
304
+ if (!activeParameter) throw new InvalidConfigurationException(`Unexpected or unnamed argument, or unknown parameter '${argument}' supplied. Please specify a parameter name (for example, --input) for each argument.`);
305
+ activeArguments.push(argument);
306
+ awaitingArguments--;
307
+ }
308
+ if (awaitingArguments === 0) finishParameter();
309
+ }
310
+ finishParameter();
311
+ for (const parameter of parameters.getParameters()) if (result[parameter.parameterId] === void 0) {
312
+ result[parameter.parameterId] = parameter.default;
313
+ const environmentValue = process.env[parameter.environmentVariableName];
314
+ if (environmentValue && !parameter.disableEnvironmentVariableOverride) result[parameter.parameterId] = parseWithProperType(environmentValue, parameter);
315
+ }
316
+ return result;
317
+ }
318
+ function unquoteString(text) {
319
+ text = text.trim();
320
+ if (text.startsWith("\"") && text.endsWith("\"") || text.startsWith("'") && text.endsWith("'") || text.startsWith("`") && text.endsWith("`")) return text.substring(1, text.length - 1);
321
+ else return text;
322
+ }
323
+ function parseStringListArgument(value) {
324
+ if (!value) return [];
325
+ return value.split(",").map((item) => unquoteString(item));
326
+ }
327
+ function parseWithProperType(value, parameterDescriptor) {
328
+ if (parameterDescriptor.type === "int") {
329
+ const parsedValue = parseInt(value, 10);
330
+ if (isNaN(parsedValue)) throw new InvalidConfigurationException(`Invalid value for parameter "${parameterDescriptor.longParameter}": Expected an integer, got "${value}".`);
331
+ return parsedValue;
332
+ } else if (parameterDescriptor.type === "bool") {
333
+ const lowerValue = value.toLowerCase();
334
+ if (lowerValue === "true" || lowerValue === "1") return true;
335
+ else if (lowerValue === "false" || lowerValue === "0") return false;
336
+ else throw new InvalidConfigurationException(`Invalid value for parameter "${parameterDescriptor.longParameter}": Expected a boolean, got "${value}".`);
337
+ } else if (parameterDescriptor.type === "string") return unquoteString(value);
338
+ else if (parameterDescriptor.type === "string[]") return parseStringListArgument(value);
339
+ else throw new InvalidConfigurationException(`Unsupported parameter type "${parameterDescriptor.type}" for parameter "${parameterDescriptor.longParameter}".`);
340
+ }
341
+ function isDefinedStringList(option) {
342
+ return Array.isArray(option) && option.every((item) => typeof item === "string");
343
+ }
344
+ /**
345
+ * Parses a configuration file content into options and unnamed arguments.
346
+ *
347
+ * @param fileContent - Content of the configuration file to parse
348
+ * @param parameters - Configuration parameters defining valid options
349
+ * @returns Object containing parsed options and unnamed arguments
350
+ * @throws Error if the configuration file contains invalid parameters or values
351
+ */
352
+ function parseConfigFile(fileContent, parameters) {
353
+ const options = {};
354
+ const unnamedArguments = [];
355
+ for (const line of fileContent.split("\n")) {
356
+ const trimmedLine = line.trim();
357
+ if (trimmedLine.startsWith("#") || trimmedLine.length === 0) continue;
358
+ if (!trimmedLine.includes("=") && parameters.getSequentialParameters().length > 0) {
359
+ unnamedArguments.push(trimmedLine);
360
+ continue;
361
+ }
362
+ const [key, value] = trimmedLine.split("=");
363
+ const parameterId = parameterNameToParameterId(key);
364
+ const parameterDescriptor = parameters.lookupParameter(parameterId);
365
+ if (parameterDescriptor) if (isDefinedStringList(options[parameterId]) && parameterDescriptor.type === "string[]") {
366
+ const newListValue = parseWithProperType(value, parameterDescriptor);
367
+ if (isDefinedStringList(newListValue)) options[parameterId] = options[parameterId].concat(newListValue);
368
+ else throw new InvalidConfigurationException(`Invalid value for parameter "${parameterDescriptor.longParameter}": Expected a list of strings, got "${value}".`);
369
+ } else options[parameterId] = parseWithProperType(value, parameterDescriptor);
370
+ else throw new InvalidConfigurationException(`Unknown configuration parameter: ${key}`);
371
+ }
372
+ return {
373
+ options,
374
+ unnamedArguments
375
+ };
376
+ }
377
+ /**
378
+ * Prints help information for all registered parameters.
379
+ *
380
+ * @param parameters - Configuration parameters to display help for
381
+ * @param aboutText - Optional descriptive text to display before parameter help
382
+ * @param printParamterId - Print the ID of the parameter instead of the command line parameter names?
383
+ */
384
+ function printHelp(parameters, aboutText, printParamterId = false) {
385
+ const lines = [];
386
+ if (aboutText) {
387
+ lines.push(aboutText);
388
+ lines.push("");
389
+ }
390
+ if (!printParamterId) lines.push("Usage:");
391
+ const grouped = groupAndSortParameterDescriptors(parameters);
392
+ for (const group of grouped) {
393
+ if (group.groupTitle) {
394
+ lines.push("");
395
+ lines.push(`# ${group.groupTitle}`);
396
+ lines.push("");
397
+ }
398
+ if (group.groupHint) {
399
+ lines.push(restrictLineLengthTo(group.groupHint, 80));
400
+ lines.push("");
401
+ }
402
+ for (const descriptor of group.parameters) {
403
+ if (descriptor.internal) continue;
404
+ function getArgumentPlaceholder() {
405
+ const singleArgumentName = descriptor.environmentVariableName;
406
+ if (descriptor.type === "bool") return "";
407
+ else if (descriptor.type === "string[]") return ` ${singleArgumentName}, .., ${singleArgumentName}`;
408
+ else return ` ${singleArgumentName}`;
409
+ }
410
+ const options = [];
411
+ if (printParamterId) options.push(`Parameter "${descriptor.parameterId}" (${descriptor.type})`);
412
+ else {
413
+ if (descriptor.shortParameter) options.push(`${descriptor.shortParameter}${getArgumentPlaceholder()}`);
414
+ options.push(`${descriptor.longParameter}${getArgumentPlaceholder()}`);
415
+ }
416
+ const help = ensureTabAfterNewline(restrictLineLengthTo(descriptor.help, 80));
417
+ const defaultValue = descriptor.default ? `Default: ${descriptor.default}` : void 0;
418
+ lines.push(` ${options.join(", ")}`);
419
+ if (help) lines.push(`\t${help}`);
420
+ if (defaultValue) lines.push(`\t${defaultValue}`);
421
+ }
422
+ }
423
+ lines.push("");
424
+ console.log(lines.join("\n"));
425
+ }
426
+ /**
427
+ * Add breaks after words (replace space by newline) in case a sentence gets longer
428
+ * on one line than the specified `maxLineLength`.
429
+ */
430
+ function restrictLineLengthTo(text, maxLineLength) {
431
+ text = text.replace(/\n/g, " ");
432
+ const words = text.split(" ");
433
+ const lines = [];
434
+ let currentLine = "";
435
+ for (const word of words) {
436
+ if (currentLine.length + word.length + 1 > maxLineLength) {
437
+ lines.push(currentLine);
438
+ currentLine = "";
439
+ }
440
+ if (currentLine.length > 0) currentLine += " ";
441
+ currentLine += word;
442
+ }
443
+ if (currentLine.length > 0) lines.push(currentLine);
444
+ return lines.join("\n");
445
+ }
446
+ function ensureTabAfterNewline(text) {
447
+ if (!text) return;
448
+ return text.replace(/\n/g, "\n ");
449
+ }
450
+ function groupAndSortParameterDescriptors(parameters) {
451
+ const parametersByGroup = /* @__PURE__ */ new Map();
452
+ const groupOrders = /* @__PURE__ */ new Map();
453
+ const groupHints = /* @__PURE__ */ new Map();
454
+ for (const parameter of parameters.getParameters()) {
455
+ const groupTitle = parameter.group?.title;
456
+ if (parameter.group) groupOrders.set(groupTitle, parameter.group.order);
457
+ if (parameter.group?.hints) groupHints.set(groupTitle, parameter.group?.hints);
458
+ const groupParameters = parametersByGroup.get(groupTitle) || [];
459
+ groupParameters.push(parameter);
460
+ parametersByGroup.set(groupTitle, groupParameters);
461
+ }
462
+ return Array.from(parametersByGroup.entries()).sort(([groupTitle1], [groupTitle2]) => {
463
+ return (groupOrders.get(groupTitle1) ?? Number.MAX_VALUE) - (groupOrders.get(groupTitle2) ?? Number.MAX_VALUE);
464
+ }).map(([groupTitle, parameters$1]) => ({
465
+ groupTitle,
466
+ groupHint: groupHints.get(groupTitle),
467
+ parameters: parameters$1.sort((a, b) => a.longParameter.localeCompare(b.longParameter))
468
+ }));
469
+ }
470
+ /**
471
+ * Runs the config parameter checks for the given options.
472
+ */
473
+ function checkArguments(configParameters, options, errorReceiver) {
474
+ let argumentsValid = true;
475
+ if (!errorReceiver) errorReceiver = (error) => {
476
+ console.error(error);
477
+ };
478
+ for (const argumentCheck of configParameters.getArgumentChecks()) {
479
+ const checkError = argumentCheck(options);
480
+ if (checkError) {
481
+ errorReceiver("Invalid configuration: " + checkError);
482
+ argumentsValid = false;
483
+ }
484
+ }
485
+ return argumentsValid;
486
+ }
487
+ /**
488
+ * Parses the command-line arguments.
489
+ */
490
+ function processCommandLine(parameters, appInfos, args, beforeCheckCallback) {
491
+ args = args ?? process.argv.slice(2);
492
+ const configParameters = parameters.clone();
493
+ configParameters.addParameter("-h", "--help", "bool", {
494
+ help: "Show this help message and exit.",
495
+ group: CONFIG_GROUP_APP,
496
+ disableEnvironmentVariableOverride: true
497
+ });
498
+ configParameters.addParameter("-v", "--version", "bool", {
499
+ help: "Show the version number and exit.",
500
+ group: CONFIG_GROUP_APP,
501
+ disableEnvironmentVariableOverride: true
502
+ });
503
+ const options = parseArguments(args, configParameters);
504
+ if (options.version) {
505
+ console.log(`${appInfos.name} v${appInfos.version}`);
506
+ process.exit(0);
507
+ }
508
+ if (options.help) {
509
+ printHelp(configParameters, appInfos.about);
510
+ process.exit(0);
511
+ }
512
+ if (beforeCheckCallback) beforeCheckCallback(options);
513
+ if (!checkArguments(configParameters, options)) {
514
+ console.error("\nArguments were missing or invalid. Please check the --help for more information. Exiting with error code 1.");
515
+ process.exit(1);
516
+ }
517
+ return options;
518
+ }
519
+ /**
520
+ * Creates a new configuration object by merging two configuration objects.
521
+ */
522
+ function parameterUnion(parameters1, parameters2) {
523
+ const result = parameters1.clone();
524
+ result.addAllOf(parameters2);
525
+ return result;
526
+ }
527
+
528
+ //#endregion
529
+ //#region src/ConfigWithOverwrites.ts
530
+ /**
531
+ * Configuration with a possibility to overwrite configuration values for this app run,
532
+ * delegating to the base configuration if no overwrite was performed.
533
+ * For type-safe access, use `makeConfigProxy` to create a proxy object.
534
+ */
535
+ var ConfigurationWithOverwrites = class ConfigurationWithOverwrites {
536
+ constructor(allParameters, redefinableParameters, baseConfiguration, overwrites) {
537
+ this.baseConfiguration = Contract.requireDefined(baseConfiguration);
538
+ this.redefinableParameters = Contract.requireDefined(redefinableParameters);
539
+ this.allParameters = Contract.requireDefined(allParameters);
540
+ this.overwrites = overwrites ?? {};
541
+ this.hash = void 0;
542
+ }
543
+ /**
544
+ * Retrieves the value of a configuration parameter based on the provided parameter ID.
545
+ */
546
+ get(parameterId) {
547
+ this.assertParameterExists(parameterId);
548
+ return this.overwrites[parameterId] ?? this.baseConfiguration[parameterId];
549
+ }
550
+ /**
551
+ * Sets the specified parameter with a given value in the `overwrites` map.
552
+ */
553
+ set(parameterId, value) {
554
+ if (this.redefinableParameters.lookupParameter(parameterId) === void 0) throw new Error(`Unknown configuration parameter: ${parameterId}`);
555
+ this.overwriteConfig(parameterId, value);
556
+ this.hash = void 0;
557
+ }
558
+ /**
559
+ * Updates or removes a configuration-overwrite for the given parameter.
560
+ */
561
+ overwriteConfig(parameter, value) {
562
+ const parameterId = parameterNameToParameterId(parameter);
563
+ this.assertParameterExists(parameterId);
564
+ if (value === void 0) {
565
+ delete this.overwrites[parameterId];
566
+ return;
567
+ }
568
+ this.overwrites[parameterId] = this.castConfigArgument(parameterId, value);
569
+ this.hash = void 0;
570
+ }
571
+ castConfigArgument(parameterId, value) {
572
+ const type = this.getParameter(parameterId).type;
573
+ if (type === "int") {
574
+ if (typeof value === "number") return Math.floor(value);
575
+ if (typeof value === "string") return parseInt(value, 10);
576
+ if (typeof value === "boolean") return value ? 1 : 0;
577
+ } else if (type === "bool") {
578
+ if (typeof value === "boolean") return value;
579
+ if (typeof value === "string") return value.toLowerCase() === "true";
580
+ if (typeof value === "number") return value !== 0;
581
+ } else if (type === "string[]") {
582
+ if (value === void 0 || value == null) return [];
583
+ if (Array.isArray(value)) return value.map(String);
584
+ if (typeof value === "string") return value.split(",").map((s) => s.trim());
585
+ } else return String(value);
586
+ throw new Error(`Cannot cast value of type ${typeof value} to ${type}`);
587
+ }
588
+ assertParameterExists(parameterId) {
589
+ if (this.allParameters.lookupParameter(parameterId) === void 0) throw new Error(`Unknown configuration parameter: ${parameterId}`);
590
+ }
591
+ getParameter(parameterId) {
592
+ const parameter = this.redefinableParameters.lookupParameter(parameterId);
593
+ if (parameter === void 0) throw new Error(`Unknown configuration parameter: ${parameterId}`);
594
+ return parameter;
595
+ }
596
+ /**
597
+ * Computes a hash for the given (possibly overwritten) configuration.
598
+ */
599
+ getHash() {
600
+ if (this.hash === void 0) {
601
+ const hash = createHash("sha256");
602
+ for (const [key, value] of Object.entries(this.baseConfiguration)) {
603
+ hash.update(key);
604
+ hash.update(String(value));
605
+ }
606
+ for (const [key, value] of Object.entries(this.overwrites)) {
607
+ hash.update(key);
608
+ hash.update(String(value));
609
+ }
610
+ this.hash = hash.digest("hex");
611
+ }
612
+ return this.hash;
613
+ }
614
+ /**
615
+ * Copies the given configuration along with the overrides.
616
+ */
617
+ copy() {
618
+ const overwritesCopy = JSON.parse(JSON.stringify(this.overwrites));
619
+ return new ConfigurationWithOverwrites(this.allParameters, this.redefinableParameters, this.baseConfiguration, overwritesCopy);
620
+ }
621
+ };
622
+ /**
623
+ * Creates a proxy object to retrieve and set config options via the attributes in the `BaseConfigType` interface.
624
+ */
625
+ function makeConfigProxy(configuration) {
626
+ return new Proxy(configuration, {
627
+ get(_, property) {
628
+ return configuration.get(property.toString());
629
+ },
630
+ set(target, property, value) {
631
+ configuration.set(property.toString(), value);
632
+ return true;
633
+ }
634
+ });
635
+ }
636
+
637
+ //#endregion
638
+ //#region src/CollectorConfig.ts
639
+ const CONFIG_GROUP_COLLECTOR_CONNECTIVITY = {
640
+ order: 1,
641
+ title: "Collector Connectivity"
642
+ };
643
+ const CONFIG_GROUP_TEAMSCALE_SERVER = {
644
+ order: 1,
645
+ title: "Teamscale Server"
646
+ };
647
+ const CONFIG_GROUP_TEAMSCALE_UPLOAD = {
648
+ order: 2,
649
+ title: "Upload to Teamscale",
650
+ hints: "NOTE: We generally recommend to set these parameters per app during the instrumentation process, that is, on the instrumenter side, for example, by setting `--config-id` or specifying single options via `--collector-option`. The following parameters are supposed to take effect if the application is instrumented with the `--coverage-target-from-collector` flag."
651
+ };
652
+ const CONFIG_GROUP_COVERAGE_DUMP = {
653
+ order: 3,
654
+ title: "Coverage Dumping"
655
+ };
656
+ const CONFIG_GROUP_ARTIFACTORY_UPLOAD = {
657
+ order: 4,
658
+ title: "Upload to Artifactory"
659
+ };
660
+ const CONFIG_GROUP_LOGGING = {
661
+ order: 5,
662
+ title: "Logging Behavior"
663
+ };
664
+ /**
665
+ * Build the collector parameters that can be overwritten by instrumented apps.
666
+ */
667
+ function buildPredefinableParameters() {
668
+ const parameters = new ConfigurationParameters();
669
+ function add(shortParameter, longParameter, type, options) {
670
+ parameters.addParameter(shortParameter, longParameter, type, options);
671
+ }
672
+ add("-k", "--keep-coverage-files", "bool", {
673
+ help: "Whether to keep the coverage files on disk after a successful upload to Teamscale.",
674
+ default: false,
675
+ group: CONFIG_GROUP_COVERAGE_DUMP
676
+ });
677
+ add("-t", "--dump-after-mins", "int", {
678
+ help: "Dump the coverage information every N minutes.",
679
+ default: 120,
680
+ group: CONFIG_GROUP_COVERAGE_DUMP
681
+ });
682
+ add(void 0, "--artifactory-server-url", "string", {
683
+ help: "Upload the coverage to the given Artifactory server URL. The URL may include a subpath on the artifactory server, e.g. https://artifactory.acme.com/my-repo/my/subpath",
684
+ group: CONFIG_GROUP_ARTIFACTORY_UPLOAD
685
+ });
686
+ add(void 0, "--artifactory-user", "string", {
687
+ help: "The user for uploading coverage to Artifactory. Only needed when not using the --artifactory-access-token option",
688
+ group: CONFIG_GROUP_ARTIFACTORY_UPLOAD
689
+ });
690
+ add(void 0, "--artifactory-password", "string", {
691
+ help: "The password for uploading coverage to Artifactory. Only needed when not using the --artifactory-access-token option",
692
+ group: CONFIG_GROUP_ARTIFACTORY_UPLOAD
693
+ });
694
+ add(void 0, "--artifactory-access-token", "string", {
695
+ help: "The access_token for uploading coverage to Artifactory.",
696
+ group: CONFIG_GROUP_ARTIFACTORY_UPLOAD
697
+ });
698
+ add(void 0, "--artifactory-path-suffix", "string", {
699
+ help: "(optional): The path within the storage location between the default path and the uploaded artifact.",
700
+ group: CONFIG_GROUP_ARTIFACTORY_UPLOAD
701
+ });
702
+ add(void 0, "--teamscale-project", "string", {
703
+ help: "The project ID to upload coverage to.",
704
+ group: CONFIG_GROUP_TEAMSCALE_UPLOAD
705
+ });
706
+ add(void 0, "--teamscale-partition", "string", {
707
+ help: "The partition to upload coverage to.",
708
+ group: CONFIG_GROUP_TEAMSCALE_UPLOAD
709
+ });
710
+ add(void 0, "--teamscale-repository", "string", {
711
+ help: "The repository to upload coverage for. Optional: Only needed when uploading via revision to a project that has more than one connector.",
712
+ group: CONFIG_GROUP_TEAMSCALE_UPLOAD
713
+ });
714
+ add(void 0, "--teamscale-message", "string", {
715
+ help: "The commit message shown within Teamscale for the coverage upload.",
716
+ default: "JavaScript coverage upload",
717
+ group: CONFIG_GROUP_TEAMSCALE_UPLOAD
718
+ });
719
+ return parameters;
720
+ }
721
+ /**
722
+ * Builds and returns the configuration parameters for the collector.
723
+ * This includes all available command line arguments and their default values.
724
+ *
725
+ * @returns {ConfigurationParameters} Configuration parameters instance with all defined options
726
+ */
727
+ function buildStaticCollectorParameters() {
728
+ const parameters = buildPredefinableParameters();
729
+ function add(shortParameter, longParameter, type, options) {
730
+ parameters.addParameter(shortParameter, longParameter, type, options);
731
+ }
732
+ add("-p", "--port", "int", {
733
+ help: "The port to receive coverage information on.",
734
+ default: 54678,
735
+ group: CONFIG_GROUP_COLLECTOR_CONNECTIVITY,
736
+ disableEnvironmentVariableOverride: true
737
+ });
738
+ add("-l", "--log-to-file", "string", {
739
+ help: "Log file",
740
+ default: "logs/collector-combined.log",
741
+ group: CONFIG_GROUP_LOGGING,
742
+ disableEnvironmentVariableOverride: true
743
+ });
744
+ add("-e", "--log-level", "string", {
745
+ help: "Log level",
746
+ default: "info",
747
+ group: CONFIG_GROUP_LOGGING,
748
+ disableEnvironmentVariableOverride: true
749
+ });
750
+ add("-j", "--json-log", "bool", {
751
+ help: "Additional JSON-like log file format.",
752
+ group: CONFIG_GROUP_LOGGING,
753
+ disableEnvironmentVariableOverride: true
754
+ });
755
+ add("-c", "--enable-control-port", "int", {
756
+ help: "Enables the remote control API on the specified port (<=0 means \"disabled\").",
757
+ default: 0,
758
+ group: CONFIG_GROUP_COLLECTOR_CONNECTIVITY
759
+ });
760
+ add("-s", "--teamscale-server-url", "string", {
761
+ help: "Upload the coverage to the given Teamscale server URL, for example, https://teamscale.dev.example.com:8080/production.",
762
+ group: CONFIG_GROUP_TEAMSCALE_SERVER
763
+ });
764
+ add("-u", "--teamscale-user", "string", {
765
+ help: "The user for uploading coverage to Teamscale.",
766
+ group: CONFIG_GROUP_TEAMSCALE_SERVER
767
+ });
768
+ add("-a", "--teamscale-access-token", "string", {
769
+ help: "The API key to use for uploading to Teamscale.",
770
+ group: CONFIG_GROUP_TEAMSCALE_SERVER
771
+ });
772
+ add(void 0, "--http-proxy", "string", {
773
+ help: "(optional): The HTTP/HTTPS proxy address that should be used in the format: http://host:port/ or http://username:password@host:port/.",
774
+ group: CONFIG_GROUP_COLLECTOR_CONNECTIVITY
775
+ });
776
+ add("-f", "--dump-folder", "string", {
777
+ help: "Target folder for coverage files.",
778
+ default: "./coverage",
779
+ group: CONFIG_GROUP_COVERAGE_DUMP
780
+ });
781
+ parameters.addArgumentCheck((options) => {
782
+ if (options.teamscaleServerUrl) {
783
+ if (!options.teamscaleUser || !options.teamscaleAccessToken) return "The Teamscale user name and access token must be given if the Teamscale server URL is given.";
784
+ }
785
+ });
786
+ return parameters;
787
+ }
788
+ /**
789
+ * Builds and returns the during run-time reconfigurable configuration parameters for the collector.
790
+ */
791
+ function buildReconfigurableCollectorParameters() {
792
+ const parameters = buildPredefinableParameters();
793
+ function addShort(shortParameter, longParameter, type, options) {
794
+ parameters.addParameter(shortParameter, longParameter, type, options);
795
+ }
796
+ function add(longArgument, type, options) {
797
+ addShort(void 0, longArgument, type, options);
798
+ }
799
+ add("--dump-to-folder", "string", {
800
+ help: "Coverage should be dumped to a folder on the server that the collector is running on.\nSpecifies the name of the subfolder within the collector's dump folder (--dump-folder of the collector) where coverage files should be placed.",
801
+ group: CONFIG_GROUP_COVERAGE_DUMP
802
+ });
803
+ return parameters;
804
+ }
805
+
806
+ //#endregion
807
+ //#region src/Validation.ts
808
+ /**
809
+ * Checks if the given string describes a commit in a valid fashion.
810
+ */
811
+ function isValidCommitInfo(commit) {
812
+ if (typeof commit === "string") {
813
+ if (/^[a-zA-Z0-9_-]+:([0-9]+|HEAD|head)$/.test(commit)) return true;
814
+ if (/^[0-9]+$/.test(commit)) return false;
815
+ if (/^[a-zA-Z0-9]{7,40}$/.test(commit)) return true;
816
+ }
817
+ return false;
818
+ }
819
+
820
+ //#endregion
821
+ export { CONFIG_GROUP_APP, ConfigurationParameters, ConfigurationWithOverwrites, Contract, IllegalArgumentException, IllegalStateException, ImplementMeException, InvalidConfigurationException, buildPredefinableParameters, buildReconfigurableCollectorParameters, buildStaticCollectorParameters, checkArguments, isValidCommitInfo, longParameterToEnvironmentVariableName, makeConfigProxy, parameterNameToParameterId, parameterUnion, parseArguments, parseConfigFile, printHelp, processCommandLine, removePrefix };
822
+ //# sourceMappingURL=index.mjs.map