@nmakarov/cli-toolkit 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/init.js ADDED
@@ -0,0 +1,1079 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/args/index.ts
9
+ import { readFileSync, existsSync } from "fs";
10
+ import { resolve, dirname, basename, extname, join, isAbsolute } from "path";
11
+ import { config } from "dotenv";
12
+ var Args = class {
13
+ args = {};
14
+ flags = {};
15
+ options = {};
16
+ commands = [];
17
+ usedKeys = /* @__PURE__ */ new Set();
18
+ aliases = {};
19
+ overrides = {};
20
+ defaults = {};
21
+ prefixes = [];
22
+ nots = [];
23
+ configValues = {};
24
+ configsLoaded = [];
25
+ env = "local";
26
+ constructor(config2 = {}) {
27
+ this.aliases = config2.aliases || {};
28
+ this.overrides = config2.overrides || {};
29
+ this.defaults = config2.defaults || {};
30
+ this.prefixes = config2.prefixes || ["not", "no"];
31
+ const args = config2.args || process.argv.slice(2);
32
+ this.parseArgs(args);
33
+ this.env = this.get("env")?.toLowerCase() || "local";
34
+ this.loadDotEnv();
35
+ this.loadConfigFiles();
36
+ this.checkConflicts();
37
+ }
38
+ /**
39
+ * Parse command line arguments
40
+ */
41
+ parseArgs(args) {
42
+ let i = 0;
43
+ while (i < args.length) {
44
+ const arg = args[i];
45
+ if (arg.startsWith("--")) {
46
+ const [key, value] = this.parseLongOption(arg);
47
+ this.setValue(key, value);
48
+ i++;
49
+ } else if (arg.startsWith("-")) {
50
+ const result = this.parseShortOption(arg, args, i);
51
+ if (result.consumed > 0) {
52
+ i += result.consumed;
53
+ } else {
54
+ i++;
55
+ }
56
+ } else {
57
+ this.commands.push(arg);
58
+ i++;
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * Parse long option (--key=value or --key)
64
+ */
65
+ parseLongOption(arg) {
66
+ const key = arg.slice(2);
67
+ const prefix = this.prefixes.find((p) => key.startsWith(p));
68
+ if (prefix) {
69
+ let strippedKey = key.slice(prefix.length);
70
+ if (strippedKey.startsWith("-")) {
71
+ strippedKey = strippedKey.slice(1);
72
+ }
73
+ this.nots.push(key);
74
+ return [strippedKey, false];
75
+ }
76
+ if (key.includes("=")) {
77
+ const eqIndex = key.indexOf("=");
78
+ const optionKey = key.slice(0, eqIndex);
79
+ const value = key.slice(eqIndex + 1);
80
+ return [optionKey, this.parseValue(value)];
81
+ } else {
82
+ return [key, true];
83
+ }
84
+ }
85
+ /**
86
+ * Parse short option (-k=value, -k, or bundled -vsd)
87
+ */
88
+ parseShortOption(arg, args, index) {
89
+ const key = arg.slice(1);
90
+ if (key.length === 1 && index + 1 < args.length && !args[index + 1].startsWith("-")) {
91
+ const value = args[index + 1];
92
+ this.setValue(key, this.parseValue(value));
93
+ return { consumed: 2 };
94
+ }
95
+ if (key.length > 1 && !key.includes("=")) {
96
+ for (let i = 0; i < key.length; i++) {
97
+ const shortKey = key[i];
98
+ if (shortKey in this.aliases) {
99
+ this.setValue(shortKey, true);
100
+ } else {
101
+ this.args[shortKey] = true;
102
+ }
103
+ }
104
+ return { consumed: 1 };
105
+ }
106
+ if (key.includes("=")) {
107
+ const eqIndex = key.indexOf("=");
108
+ const optionKey = key.slice(0, eqIndex);
109
+ const value = key.slice(eqIndex + 1);
110
+ if (optionKey.length > 1) {
111
+ for (let i = 0; i < optionKey.length - 1; i++) {
112
+ const shortKey = optionKey[i];
113
+ if (shortKey in this.aliases) {
114
+ this.setValue(shortKey, true);
115
+ } else {
116
+ this.args[shortKey] = true;
117
+ }
118
+ }
119
+ const lastKey = optionKey[optionKey.length - 1];
120
+ if (lastKey in this.aliases) {
121
+ this.setValue(lastKey, this.parseValue(value));
122
+ } else {
123
+ this.args[lastKey] = this.parseValue(value);
124
+ }
125
+ } else {
126
+ this.setValue(optionKey, this.parseValue(value));
127
+ }
128
+ return { consumed: 1 };
129
+ } else {
130
+ this.setValue(key, true);
131
+ return { consumed: 1 };
132
+ }
133
+ }
134
+ /**
135
+ * Parse value (handle quotes)
136
+ */
137
+ parseValue(value) {
138
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
139
+ return value.slice(1, -1);
140
+ }
141
+ return value;
142
+ }
143
+ /**
144
+ * Set a value with proper categorization
145
+ */
146
+ setValue(key, value) {
147
+ const resolvedKey = this.aliases[key] || key;
148
+ if (typeof value === "boolean") {
149
+ this.flags[resolvedKey] = value;
150
+ } else {
151
+ this.options[resolvedKey] = value;
152
+ }
153
+ this.args[resolvedKey.toLowerCase()] = value;
154
+ }
155
+ /**
156
+ * Check for conflicts (short + long form of same option)
157
+ */
158
+ checkConflicts() {
159
+ const conflicts = [];
160
+ for (const [shortKey, longKey] of Object.entries(this.aliases)) {
161
+ const hasShort = this.args[shortKey] !== void 0;
162
+ const hasLong = this.args[longKey] !== void 0;
163
+ if (hasShort && hasLong) {
164
+ conflicts.push(`Both -${shortKey} and --${longKey} specified`);
165
+ }
166
+ }
167
+ if (conflicts.length > 0) {
168
+ throw new Error(`Argument conflicts: ${conflicts.join(", ")}`);
169
+ }
170
+ }
171
+ /**
172
+ * Get a value with precedence order
173
+ */
174
+ get(key) {
175
+ const resolvedKey = this.aliases[key] || key;
176
+ this.usedKeys.add(resolvedKey);
177
+ if (this.overrides[resolvedKey] !== void 0) {
178
+ return this.overrides[resolvedKey];
179
+ }
180
+ const lcKey = resolvedKey.toLowerCase();
181
+ const lcKeyWithEnv = `${lcKey}${this.env ? `_${this.env.toLowerCase()}` : ""}`;
182
+ if (this.env && this.args[lcKeyWithEnv] !== void 0) {
183
+ return this.args[lcKeyWithEnv];
184
+ } else if (this.args[lcKey] !== void 0) {
185
+ return this.args[lcKey];
186
+ }
187
+ if (this.configValues[resolvedKey] !== void 0) {
188
+ return this.configValues[resolvedKey];
189
+ }
190
+ const envKey = this.toEnvKey(resolvedKey);
191
+ const envKeyWithEnv = `${envKey}${this.env ? `_${this.env.toUpperCase()}` : ""}`;
192
+ const envSpecificKey = Object.keys(process.env).find(
193
+ (k) => this.env && k.toUpperCase() === envKeyWithEnv
194
+ );
195
+ const envKeyFound = Object.keys(process.env).find((k) => k.toUpperCase() === envKey);
196
+ if (envSpecificKey) {
197
+ return process.env[envSpecificKey];
198
+ } else if (envKeyFound) {
199
+ return process.env[envKeyFound];
200
+ }
201
+ if (this.defaults[resolvedKey] !== void 0) {
202
+ return this.defaults[resolvedKey];
203
+ }
204
+ if (resolvedKey === "env" && process.env.NODE_ENV !== void 0) {
205
+ return process.env.NODE_ENV;
206
+ }
207
+ return void 0;
208
+ }
209
+ /**
210
+ * Set a value (for testing/internal use)
211
+ */
212
+ set(key, value) {
213
+ this.args[key] = value;
214
+ }
215
+ /**
216
+ * Check if a command exists (case-insensitive)
217
+ */
218
+ hasCommand(cmd) {
219
+ return this.commands.some((command) => command.toLowerCase() === cmd.toLowerCase());
220
+ }
221
+ /**
222
+ * Get all commands
223
+ */
224
+ getCommands() {
225
+ return [...this.commands];
226
+ }
227
+ /**
228
+ * Get used keys (as array)
229
+ */
230
+ getUsed() {
231
+ return Array.from(this.usedKeys);
232
+ }
233
+ /**
234
+ * Get unused keys (as array)
235
+ */
236
+ getUnused() {
237
+ const unused = [];
238
+ for (const key of Object.keys(this.args)) {
239
+ if (!this.usedKeys.has(key) && !this.nots.includes(key)) {
240
+ unused.push(key);
241
+ }
242
+ }
243
+ return unused;
244
+ }
245
+ /**
246
+ * Convert key to environment variable format
247
+ */
248
+ toEnvKey(key) {
249
+ return key.replace(
250
+ /[A-Z0-9]/g,
251
+ (match, offset) => offset === 0 ? match : "_" + match.toLowerCase()
252
+ ).toUpperCase();
253
+ }
254
+ /**
255
+ * Load .env file
256
+ */
257
+ loadDotEnv() {
258
+ const dotEnvPath = this.get("dotEnvPath") || process.cwd();
259
+ const dotEnvFile = this.get("dotEnvFile") || ".env";
260
+ if (this.get("dotEnvFile")) {
261
+ const customPath = resolve(dotEnvPath, dotEnvFile);
262
+ if (existsSync(customPath)) {
263
+ config({ path: customPath, quiet: true });
264
+ }
265
+ return;
266
+ }
267
+ let dotEnvPathFile = null;
268
+ const envSpecificFile = `.env.${this.env}`;
269
+ const envSpecificPath = resolve(dotEnvPath, envSpecificFile);
270
+ if (existsSync(envSpecificPath)) {
271
+ dotEnvPathFile = envSpecificPath;
272
+ }
273
+ if (!dotEnvPathFile && !this.get("dotEnvPath")) {
274
+ const examplesPath = resolve(dotEnvPath, "examples");
275
+ const examplesEnvSpecificPath = resolve(examplesPath, envSpecificFile);
276
+ if (existsSync(examplesEnvSpecificPath)) {
277
+ dotEnvPathFile = examplesEnvSpecificPath;
278
+ }
279
+ }
280
+ if (!dotEnvPathFile) {
281
+ dotEnvPathFile = resolve(dotEnvPath, dotEnvFile);
282
+ if (!existsSync(dotEnvPathFile)) {
283
+ if (!this.get("dotEnvPath")) {
284
+ const examplesPath = resolve(dotEnvPath, "examples");
285
+ const examplesEnvFile = resolve(examplesPath, dotEnvFile);
286
+ if (existsSync(examplesEnvFile)) {
287
+ dotEnvPathFile = examplesEnvFile;
288
+ } else {
289
+ dotEnvPathFile = resolve(dotEnvPath, "..", dotEnvFile);
290
+ }
291
+ }
292
+ }
293
+ }
294
+ if (dotEnvPathFile && existsSync(dotEnvPathFile)) {
295
+ config({ path: dotEnvPathFile, quiet: true });
296
+ }
297
+ }
298
+ /**
299
+ * Load configuration files
300
+ */
301
+ loadConfigFiles() {
302
+ this.configsLoaded = [];
303
+ this.configValues = {};
304
+ const _defaultConfigExtension = this.get("defaultConfigExtension") || "js";
305
+ const optConfigFiles = this.get("config") || this.get("configs") || "";
306
+ const configFiles = optConfigFiles ? optConfigFiles.split(/,\s*/) : [];
307
+ const optConfigFilePath = this.get("configPath");
308
+ if (configFiles.length > 0) {
309
+ for (const cfgFile of configFiles) {
310
+ let notLoaded = false;
311
+ let notLoadedEnvSpecific = false;
312
+ const cfgFileWithPath = this.resolveFileWithPath(optConfigFilePath, cfgFile);
313
+ try {
314
+ const cfgContents = this.requireConfigFile(cfgFileWithPath);
315
+ this.configValues = { ...this.configValues, ...cfgContents };
316
+ this.configsLoaded.push(cfgFileWithPath);
317
+ } catch {
318
+ notLoaded = true;
319
+ }
320
+ const cfgEnvFileWithPath = this.resolveFileWithPath(
321
+ optConfigFilePath,
322
+ cfgFile,
323
+ this.env
324
+ );
325
+ if (cfgEnvFileWithPath !== cfgFileWithPath) {
326
+ try {
327
+ const cfgContents = this.requireConfigFile(cfgEnvFileWithPath);
328
+ this.configValues = { ...this.configValues, ...cfgContents };
329
+ this.configsLoaded.push(cfgEnvFileWithPath);
330
+ } catch {
331
+ notLoadedEnvSpecific = true;
332
+ }
333
+ } else {
334
+ notLoadedEnvSpecific = true;
335
+ }
336
+ if (notLoaded && notLoadedEnvSpecific) {
337
+ throw new Error(`can't load config file "${cfgFileWithPath}"`);
338
+ }
339
+ }
340
+ }
341
+ }
342
+ /**
343
+ * Resolve file path with environment-specific naming
344
+ */
345
+ resolveFileWithPath(optConfigFilePath, cfgFile, env) {
346
+ let cfgFileWithPath = optConfigFilePath ? isAbsolute(optConfigFilePath) ? resolve(optConfigFilePath, cfgFile) : resolve(process.cwd(), optConfigFilePath, cfgFile) : isAbsolute(cfgFile) ? cfgFile : resolve(process.cwd(), cfgFile);
347
+ const { basePathWithName, extension } = this.splitPath(cfgFileWithPath);
348
+ if (env) {
349
+ cfgFileWithPath = `${basePathWithName}.${env}.${extension || "js"}`;
350
+ } else {
351
+ cfgFileWithPath = `${basePathWithName}.${extension || "js"}`;
352
+ }
353
+ return cfgFileWithPath;
354
+ }
355
+ /**
356
+ * Split file path into base path and extension
357
+ */
358
+ splitPath(filePath) {
359
+ const basePathWithName = join(dirname(filePath), basename(filePath, extname(filePath)));
360
+ const extension = extname(filePath).slice(1);
361
+ return { basePathWithName, extension };
362
+ }
363
+ /**
364
+ * Require a configuration file (supports .js and .json)
365
+ */
366
+ requireConfigFile(filePath) {
367
+ if (!existsSync(filePath)) {
368
+ throw new Error(`Config file not found: ${filePath}`);
369
+ }
370
+ const ext = extname(filePath).toLowerCase();
371
+ if (ext === ".json") {
372
+ const content = readFileSync(filePath, "utf8");
373
+ return JSON.parse(content);
374
+ } else if (ext === ".js") {
375
+ try {
376
+ delete __require.cache[__require.resolve(filePath)];
377
+ return __require(filePath);
378
+ } catch (error) {
379
+ throw new Error(`Failed to load JS config file: ${error instanceof Error ? error.message : String(error)}`);
380
+ }
381
+ } else {
382
+ throw new Error(`Unsupported file extension: ${ext}`);
383
+ }
384
+ }
385
+ /**
386
+ * Get all parsed data
387
+ */
388
+ getParsed() {
389
+ return {
390
+ command: this.commands[0] || "",
391
+ flags: { ...this.flags },
392
+ options: { ...this.options },
393
+ usedKeys: Array.from(this.usedKeys)
394
+ };
395
+ }
396
+ /**
397
+ * Set prefixes dynamically and re-parse arguments (like legacy)
398
+ */
399
+ setPrefixes(prefixes) {
400
+ const arr = Array.isArray(prefixes) ? prefixes : prefixes.split(/,\s*/);
401
+ const sortedArr = arr.sort(
402
+ (a, b) => a.length < b.length ? 1 : a.length > b.length ? -1 : 0
403
+ );
404
+ this.prefixes = sortedArr.map((el) => el.toLowerCase());
405
+ const args = process.argv.slice(2);
406
+ this.parseArgs(args);
407
+ }
408
+ };
409
+
410
+ // src/params/index.ts
411
+ import Joi from "joi";
412
+
413
+ // src/errors.ts
414
+ var FrameworkError = class extends Error {
415
+ constructor(message) {
416
+ super(message);
417
+ this.name = "FrameworkError";
418
+ }
419
+ };
420
+ var ParamError = class extends FrameworkError {
421
+ constructor(message) {
422
+ super(message);
423
+ this.name = "ParamError";
424
+ }
425
+ };
426
+ var InitError = class extends FrameworkError {
427
+ constructor(message) {
428
+ super(message);
429
+ this.name = "InitError";
430
+ }
431
+ };
432
+
433
+ // src/params/custom-types.ts
434
+ var joiEdateType = (value, helpers) => {
435
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(value)) {
436
+ const testDate = new Date(value);
437
+ if (!isNaN(testDate.getTime())) {
438
+ return value;
439
+ }
440
+ }
441
+ if (value instanceof Date) {
442
+ return value.toISOString();
443
+ }
444
+ if (typeof value !== "string") {
445
+ value = String(value);
446
+ }
447
+ if (value.toLowerCase() === "now") {
448
+ return (/* @__PURE__ */ new Date()).toISOString();
449
+ }
450
+ const referenceRegex = /^@(\w+)([+-]\d+[smhdwy])$/i;
451
+ const referenceMatch = value.match(referenceRegex);
452
+ if (referenceMatch) {
453
+ const [, paramName, relativeExpr] = referenceMatch;
454
+ const context = helpers.prefs?.context;
455
+ if (!context || !context.params) {
456
+ throw new ParamError(`Cannot resolve cross-parameter reference @${paramName}: context not available. Ensure parameters are processed with proper context.`);
457
+ }
458
+ const referencedValue = context.params[paramName];
459
+ if (referencedValue === void 0 || referencedValue === null) {
460
+ throw new ParamError(`Cannot resolve @${paramName}: parameter "${paramName}" is not defined or has no value. Parameters are evaluated left-to-right.`);
461
+ }
462
+ let referenceDate;
463
+ if (referencedValue instanceof Date) {
464
+ referenceDate = referencedValue;
465
+ } else if (typeof referencedValue === "string") {
466
+ referenceDate = new Date(referencedValue);
467
+ if (isNaN(referenceDate.getTime())) {
468
+ throw new ParamError(`Referenced parameter @${paramName} has invalid date value: ${referencedValue}`);
469
+ }
470
+ } else {
471
+ throw new ParamError(`Referenced parameter @${paramName} is not a valid date type (found: ${typeof referencedValue})`);
472
+ }
473
+ const relativeMatch2 = relativeExpr.match(/^([+-])(\d+)([smhdwy])$/i);
474
+ if (!relativeMatch2) {
475
+ throw new ParamError(`Invalid relative time expression in @${paramName}${relativeExpr}`);
476
+ }
477
+ const [, sign, amount, unit] = relativeMatch2;
478
+ const offset = calculateTimeOffset(parseInt(amount, 10), unit, sign);
479
+ const resultDate = new Date(referenceDate.getTime() + offset);
480
+ return resultDate.toISOString();
481
+ }
482
+ const relativeTimeRegex = /^([+-])(\d+)([smhdwy])$/i;
483
+ const relativeMatch = value.match(relativeTimeRegex);
484
+ if (relativeMatch) {
485
+ const [, sign, amount, unit] = relativeMatch;
486
+ const numAmount = parseInt(amount, 10);
487
+ if (isNaN(numAmount)) {
488
+ throw new ParamError(`Invalid relative time amount: ${amount}`);
489
+ }
490
+ const offset = calculateTimeOffset(numAmount, unit, sign);
491
+ const resultDate = new Date(Date.now() + offset);
492
+ return resultDate.toISOString();
493
+ }
494
+ const parsedDate = new Date(value);
495
+ if (isNaN(parsedDate.getTime())) {
496
+ throw new ParamError(`Invalid date format: ${value}. Expected a valid date string, "now", relative time expression (e.g., "-2h", "+1d"), or cross-parameter reference (e.g., "@startTime+2h")`);
497
+ }
498
+ return parsedDate.toISOString();
499
+ };
500
+ function calculateTimeOffset(amount, unit, sign) {
501
+ let multiplier = 1;
502
+ switch (unit.toLowerCase()) {
503
+ case "s":
504
+ multiplier = 1e3;
505
+ break;
506
+ case "m":
507
+ multiplier = 60 * 1e3;
508
+ break;
509
+ case "h":
510
+ multiplier = 60 * 60 * 1e3;
511
+ break;
512
+ case "d":
513
+ multiplier = 24 * 60 * 60 * 1e3;
514
+ break;
515
+ case "w":
516
+ multiplier = 7 * 24 * 60 * 60 * 1e3;
517
+ break;
518
+ case "y":
519
+ multiplier = 365 * 24 * 60 * 60 * 1e3;
520
+ break;
521
+ default:
522
+ throw new ParamError(`Invalid time unit: ${unit}. Supported units: s, m, h, d, w, y`);
523
+ }
524
+ return sign === "+" ? amount * multiplier : -amount * multiplier;
525
+ }
526
+ var joiStringArrayType = (type) => (value, helpers) => {
527
+ if (value === void 0 || typeof value === "function") {
528
+ return [];
529
+ }
530
+ const arr = value.split(/,\s*/).map((el) => {
531
+ if (type === "number") {
532
+ const v = parseInt(el, 10);
533
+ if (isNaN(v)) {
534
+ throw new ParamError(`array element "${el}" should be numeric`);
535
+ }
536
+ return v;
537
+ } else if (type === "boolean") {
538
+ const v = el.match(/true|t|yes|1/i) ? true : el.match(/false|f|no|0/i) ? false : null;
539
+ if (v === null) {
540
+ throw new ParamError(`array element "${el}" should be boolean`);
541
+ }
542
+ return v;
543
+ } else if (type === "string") {
544
+ return el;
545
+ } else {
546
+ throw new ParamError(`unknown type "${type}" for array elements`);
547
+ }
548
+ });
549
+ return arr;
550
+ };
551
+
552
+ // src/params/index.ts
553
+ var Params = class {
554
+ params = {};
555
+ definitions = {};
556
+ args;
557
+ paramSetters = [];
558
+ paramGetters = [];
559
+ constructor({ args }, opts = {}) {
560
+ this.args = args;
561
+ for (const [k, v] of Object.entries(opts)) {
562
+ this.params[k] = v;
563
+ }
564
+ }
565
+ /**
566
+ * Assign a parameter definition
567
+ */
568
+ assignDefinition(key, definition) {
569
+ if (this.definitions[key] && !definition) {
570
+ return this.definitions[key];
571
+ }
572
+ let type;
573
+ if (!definition) {
574
+ type = Joi.string();
575
+ } else if (Joi.isSchema(definition)) {
576
+ type = definition;
577
+ } else if (Joi.isSchema(definition.type)) {
578
+ type = definition.type;
579
+ } else if (typeof definition === "string") {
580
+ type = this.toJoi(definition);
581
+ } else if (typeof definition.type === "string") {
582
+ type = this.toJoi(definition.type);
583
+ } else if (!definition.type) {
584
+ type = Joi.string();
585
+ } else {
586
+ type = Joi.string();
587
+ }
588
+ if (!this.definitions[key]) {
589
+ this.definitions[key] = {};
590
+ }
591
+ this.definitions[key].type = type;
592
+ if (definition && definition.values) {
593
+ if (Array.isArray(definition.values)) {
594
+ this.definitions[key].values = definition.values;
595
+ }
596
+ }
597
+ return this.definitions[key];
598
+ }
599
+ /**
600
+ * Convert string definition to Joi schema
601
+ */
602
+ toJoi(str) {
603
+ let type;
604
+ if (str.match(/^string|^text/i)) {
605
+ type = Joi.string();
606
+ } else if (str.match(/^number|^integer|^int/i)) {
607
+ type = Joi.number();
608
+ } else if (str.match(/^boolean|^bool/i)) {
609
+ type = Joi.boolean();
610
+ } else if (str.match(/^date/i)) {
611
+ type = Joi.custom(joiEdateType);
612
+ } else if (str.match(/^duration/i)) {
613
+ type = Joi.string().isoDuration();
614
+ } else if (str.match(/^array/i)) {
615
+ let elementTypes = "string";
616
+ const tmp = str.match(/\((.*)\)/);
617
+ if (tmp && tmp[1].match(/string/i)) {
618
+ elementTypes = "string";
619
+ } else if (tmp && tmp[1].match(/number|integer|int/i)) {
620
+ elementTypes = "number";
621
+ } else if (tmp && tmp[1].match(/boolean|bool/i)) {
622
+ elementTypes = "boolean";
623
+ }
624
+ type = Joi.custom(joiStringArrayType(elementTypes));
625
+ } else {
626
+ type = Joi.string();
627
+ }
628
+ const regexForDefault = /\bdefault\s+([^\s]+)/;
629
+ const matchForDefault = str.match(regexForDefault);
630
+ if (matchForDefault) {
631
+ const defValObj = type.validate(matchForDefault[1]);
632
+ if (defValObj.error) {
633
+ throw new ParamError(`default value "${defValObj.value}" type mismatch`);
634
+ }
635
+ type = type.default(defValObj.value);
636
+ } else if (str.match(/required/)) {
637
+ type = type.required();
638
+ }
639
+ return type;
640
+ }
641
+ /**
642
+ * Validate a value against a definition
643
+ */
644
+ validate(key, val, def) {
645
+ const { value, error } = def.type.validate(val, { context: { params: this.params } });
646
+ if (error) {
647
+ const errs = error.details.map((el) => el.message).join(", ");
648
+ throw new ParamError(`"${key}" validation error: ${errs}`);
649
+ }
650
+ return value;
651
+ }
652
+ /**
653
+ * Get a parameter value with validation
654
+ */
655
+ get(key, definition) {
656
+ const def = this.assignDefinition(key, definition);
657
+ let valFromGetters = void 0;
658
+ if (def.volatile || true) {
659
+ valFromGetters = this.runAllRegisteredGetters(key);
660
+ }
661
+ const valFromArgs = this.args.get(key);
662
+ const valFromParams = this.params[key];
663
+ const res = valFromGetters ? this.validate(key, valFromGetters, def) : valFromArgs ? this.validate(key, valFromArgs, def) : this.validate(key, valFromParams, def);
664
+ if (res !== void 0 && def.values && !def.values.includes(res)) {
665
+ throw new ParamError(`key ${key} should be one of ${def.values}`);
666
+ }
667
+ return res;
668
+ }
669
+ /**
670
+ * Set a parameter value with validation
671
+ */
672
+ set(key, val, definition) {
673
+ if (val && val.type && val.value) {
674
+ definition = val;
675
+ val = val.value;
676
+ }
677
+ const def = this.assignDefinition(key, definition);
678
+ if (!this.runAllRegisteredSetters(key, val)) {
679
+ this.params[key] = val;
680
+ }
681
+ }
682
+ /**
683
+ * Get all parameters from definitions
684
+ * Processes parameters left-to-right to support cross-parameter references
685
+ */
686
+ getAll(defs) {
687
+ const res = {};
688
+ for (const [k, def] of Object.entries(defs)) {
689
+ const value = this.get(k, def);
690
+ res[k] = value;
691
+ if (value !== void 0) {
692
+ this.params[k] = value;
693
+ }
694
+ }
695
+ return res;
696
+ }
697
+ /**
698
+ * Run all registered getters for a key
699
+ */
700
+ runAllRegisteredGetters(key) {
701
+ let val = null;
702
+ for (const getter of this.paramGetters) {
703
+ val = getter(key, this.definitions[key]);
704
+ if (val !== void 0) {
705
+ break;
706
+ }
707
+ }
708
+ return val;
709
+ }
710
+ /**
711
+ * Run all registered setters for a key
712
+ */
713
+ runAllRegisteredSetters(key, value) {
714
+ let setterUsed = false;
715
+ for (const setter of this.paramSetters) {
716
+ setterUsed = setter(key, value);
717
+ if (setterUsed) {
718
+ break;
719
+ }
720
+ }
721
+ return setterUsed;
722
+ }
723
+ /**
724
+ * Register a parameter getter
725
+ */
726
+ registerParamGetter(fn) {
727
+ this.paramGetters.push(fn);
728
+ }
729
+ /**
730
+ * Register a parameter setter
731
+ */
732
+ registerParamSetter(fn) {
733
+ this.paramSetters.push(fn);
734
+ }
735
+ };
736
+
737
+ // src/logger/index.ts
738
+ import chalk from "chalk";
739
+ import util from "util";
740
+
741
+ // src/logger/transports.ts
742
+ var ConsoleTransport = class {
743
+ write(payload) {
744
+ console.info(payload);
745
+ }
746
+ };
747
+ var ParentProcessTransport = class {
748
+ write(payload) {
749
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
750
+ console.info(payload);
751
+ return;
752
+ }
753
+ if (typeof process.send === "function" && process.connected === true) {
754
+ process.send(payload);
755
+ } else {
756
+ console.info(payload);
757
+ }
758
+ }
759
+ };
760
+
761
+ // src/logger/index.ts
762
+ var ALL_LEVELS = [
763
+ "silly",
764
+ "debug",
765
+ "logic",
766
+ "info",
767
+ "notice",
768
+ "warn",
769
+ "error",
770
+ "results",
771
+ "request",
772
+ "response",
773
+ "progress"
774
+ ];
775
+ var LEVEL_COLORS = {
776
+ error: chalk.red.bold,
777
+ warn: chalk.rgb(255, 165, 0),
778
+ notice: chalk.cyan,
779
+ info: chalk.white.bold,
780
+ logic: chalk.gray,
781
+ debug: chalk.gray,
782
+ silly: chalk.gray,
783
+ request: chalk.green,
784
+ response: chalk.yellow,
785
+ progress: chalk.green,
786
+ results: chalk.magenta
787
+ };
788
+ var CliToolkitLogger = class {
789
+ options;
790
+ transport;
791
+ startTimes = {};
792
+ lastProgressTimes = {};
793
+ constructor(options = {}) {
794
+ this.options = this.normalizeOptions(options);
795
+ this.transport = this.options.route === "ipc" ? new ParentProcessTransport() : new ConsoleTransport();
796
+ }
797
+ setMode(mode) {
798
+ if (!this.isValidMode(mode)) {
799
+ throw new Error(`Unsupported logger mode: ${mode}`);
800
+ }
801
+ this.options.mode = mode;
802
+ }
803
+ debug(message, ...chunks) {
804
+ this.out({ level: "debug", message, chunks });
805
+ }
806
+ info(message, ...chunks) {
807
+ this.out({ level: "info", message, chunks });
808
+ }
809
+ notice(message, ...chunks) {
810
+ this.out({ level: "notice", message, chunks });
811
+ }
812
+ warn(message, ...chunks) {
813
+ this.out({ level: "warn", message, chunks });
814
+ }
815
+ error(message, ...chunks) {
816
+ this.out({ level: "error", message, chunks });
817
+ }
818
+ logic(message, ...chunks) {
819
+ this.out({ level: "logic", message, chunks });
820
+ }
821
+ silly(message, ...chunks) {
822
+ this.out({ level: "silly", message, chunks });
823
+ }
824
+ results(results) {
825
+ this.out({ level: "results", message: "results", results });
826
+ }
827
+ request(operation, ...chunks) {
828
+ const message = this.inspectChunks([operation, ...chunks]);
829
+ this.out({ level: "request", message });
830
+ }
831
+ response(operation, ...chunks) {
832
+ const message = this.inspectChunks([operation, ...chunks]);
833
+ this.out({ level: "response", message });
834
+ }
835
+ progress(message, opts) {
836
+ const { prefix, count, total } = opts;
837
+ const paddedTotal = String(total).length;
838
+ const paddedCount = String(count).padStart(paddedTotal, " ");
839
+ const payload = {
840
+ level: "progress",
841
+ message,
842
+ count: paddedCount,
843
+ total,
844
+ prefix
845
+ };
846
+ if (!this.startTimes[prefix ?? ""]) {
847
+ this.startTimes[prefix ?? ""] = Date.now();
848
+ }
849
+ if (this.options.progressTimes) {
850
+ const elapsedSeconds = (Date.now() - this.startTimes[prefix ?? ""]) / 1e3;
851
+ let remaining = -1;
852
+ if (count > 1) {
853
+ const rate = elapsedSeconds / (count - 1);
854
+ remaining = (total - count) * rate;
855
+ }
856
+ payload.elapsed = this.round(elapsedSeconds, 2);
857
+ payload.remaining = remaining >= 0 ? this.round(remaining, 2) : remaining;
858
+ }
859
+ if (count >= total) {
860
+ delete this.startTimes[prefix ?? ""];
861
+ delete this.lastProgressTimes[prefix ?? ""];
862
+ }
863
+ if (this.shouldOutputProgress(prefix ?? "", count, total)) {
864
+ this.out(payload);
865
+ if (this.options.progressThrottle && prefix) {
866
+ this.lastProgressTimes[prefix] = Date.now();
867
+ }
868
+ }
869
+ }
870
+ shouldOutputProgress(prefix, count, total) {
871
+ if (!this.options.progressThrottle) {
872
+ return true;
873
+ }
874
+ if (count === 1 || count === total || !prefix) {
875
+ return true;
876
+ }
877
+ const lastTime = this.lastProgressTimes[prefix];
878
+ if (!lastTime) {
879
+ return true;
880
+ }
881
+ return Date.now() - lastTime >= this.options.progressThrottle;
882
+ }
883
+ out(struct) {
884
+ if (this.options.silent) {
885
+ return;
886
+ }
887
+ if (!this.options.levels.includes(struct.level)) {
888
+ return;
889
+ }
890
+ if (this.options.prefix && !struct.prefix) {
891
+ struct.prefix = this.options.prefix;
892
+ }
893
+ const output = this.options.mode === "json" ? struct : this.formatLog(struct);
894
+ this.transport.write(output);
895
+ }
896
+ formatLog(struct) {
897
+ const parts = [];
898
+ const now = /* @__PURE__ */ new Date();
899
+ if (this.options.timestamp) {
900
+ parts.push(now.toISOString());
901
+ }
902
+ if (this.options.showLevel) {
903
+ parts.push(struct.level.toUpperCase());
904
+ }
905
+ if (struct.level === "progress") {
906
+ if (struct.prefix) {
907
+ parts.push(LEVEL_COLORS[struct.level].bold(struct.prefix));
908
+ }
909
+ if (struct.count !== void 0 && struct.total !== void 0) {
910
+ parts.push(LEVEL_COLORS[struct.level](`${struct.count}/${struct.total}`));
911
+ }
912
+ } else if (struct.prefix) {
913
+ parts.push(chalk.cyan(`[${struct.prefix}]`));
914
+ }
915
+ if (struct.message) {
916
+ const formatter = LEVEL_COLORS[struct.level] ?? chalk.white;
917
+ parts.push(formatter.bold(struct.message));
918
+ }
919
+ if (struct.level === "progress") {
920
+ if (struct.elapsed !== void 0 && struct.remaining !== void 0) {
921
+ const formatter = LEVEL_COLORS[struct.level];
922
+ parts.push(formatter(`${struct.elapsed}/${struct.remaining}`));
923
+ }
924
+ }
925
+ if (struct.chunks && struct.chunks.length) {
926
+ parts.push(this.inspectChunks(struct.chunks));
927
+ }
928
+ if (struct.results) {
929
+ const formatter = LEVEL_COLORS[struct.level] ?? chalk.white;
930
+ parts.push(formatter(JSON.stringify(struct.results, null, 4)));
931
+ }
932
+ return parts.join(" ");
933
+ }
934
+ inspectChunks(chunks) {
935
+ return chunks.map((chunk) => util.inspect(chunk, { colors: true, depth: null })).join(" ");
936
+ }
937
+ normalizeOptions(options) {
938
+ const { route, mode, prefix, silent, showLevel, timestamp, levels, progress } = options;
939
+ const shouldUseIpc = this.shouldUseIpcRoute();
940
+ const normalized = {
941
+ mode: this.isValidMode(mode) ? mode : "text",
942
+ route: route ?? (shouldUseIpc ? "ipc" : "console"),
943
+ prefix,
944
+ silent: silent ?? false,
945
+ showLevel: showLevel ?? true,
946
+ timestamp: timestamp ?? false,
947
+ levels: this.normalizeLevels(levels),
948
+ progressTimes: progress?.withTimes ?? false,
949
+ progressThrottle: progress?.throttleMs
950
+ };
951
+ return normalized;
952
+ }
953
+ shouldUseIpcRoute() {
954
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
955
+ return false;
956
+ }
957
+ return typeof process.send === "function" && process.connected === true;
958
+ }
959
+ normalizeLevels(levels) {
960
+ if (!levels || !levels.length) {
961
+ return ALL_LEVELS;
962
+ }
963
+ const includes = levels.filter((level) => !level.startsWith("-"));
964
+ const excludes = levels.filter((level) => level.startsWith("-")).map((level) => level.slice(1));
965
+ const unknown = [...includes, ...excludes].filter((level) => !ALL_LEVELS.includes(level));
966
+ if (unknown.length) {
967
+ console.warn(`[Logger] Unknown level(s): ${unknown.join(", ")}`);
968
+ }
969
+ const base = includes.length ? includes : ALL_LEVELS;
970
+ return base.filter((level) => !excludes.includes(level));
971
+ }
972
+ isValidMode(mode) {
973
+ return mode === void 0 || mode === null || mode === "text" || mode === "json";
974
+ }
975
+ round(value, places) {
976
+ const factor = Math.pow(10, places);
977
+ return Math.round(value * factor) / factor;
978
+ }
979
+ };
980
+
981
+ // src/init/index.ts
982
+ import { EventEmitter } from "events";
983
+ function setup(opts = {}) {
984
+ const args = new Args({
985
+ overrides: opts.overrides || {},
986
+ defaults: opts.defaults || {}
987
+ });
988
+ const params = new Params({ args }, opts.overrides || {});
989
+ const loggerOptions = opts.logger || {};
990
+ const logger = new CliToolkitLogger({
991
+ mode: loggerOptions.mode || "text",
992
+ route: loggerOptions.route || "console",
993
+ prefix: loggerOptions.prefix,
994
+ silent: loggerOptions.silent,
995
+ showLevel: loggerOptions.showLevel,
996
+ timestamp: loggerOptions.timestamp,
997
+ levels: loggerOptions.levels
998
+ });
999
+ const cleanupFunctions = [];
1000
+ const context = {
1001
+ args,
1002
+ params,
1003
+ logger,
1004
+ emitter: new EventEmitter(),
1005
+ isStop: () => false,
1006
+ // Will be set in init function
1007
+ cleanupFunctions,
1008
+ registerCleanup: (fn) => {
1009
+ cleanupFunctions.push(fn);
1010
+ }
1011
+ };
1012
+ logger.debug("[setup] completed successfully");
1013
+ return context;
1014
+ }
1015
+ async function setupModules(context, opts = {}) {
1016
+ if (opts.modules && opts.modules.length > 0) {
1017
+ context.logger.debug(`[setupModules] modules specified: ${opts.modules.join(", ")} (not yet implemented)`);
1018
+ }
1019
+ context.logger.debug("[setupModules] completed successfully");
1020
+ return context;
1021
+ }
1022
+ async function init(flow, opts = {}) {
1023
+ let stop = false;
1024
+ let context = null;
1025
+ try {
1026
+ context = setup(opts);
1027
+ context.isStop = () => stop;
1028
+ context = await setupModules(context, opts);
1029
+ process.on("SIGINT", async () => {
1030
+ if (stop) {
1031
+ context.logger.warn("[process] killed");
1032
+ process.exit(2);
1033
+ }
1034
+ stop = true;
1035
+ let allowance = 5;
1036
+ try {
1037
+ allowance = context.params.get("stopAllowance", "number default 5");
1038
+ } catch {
1039
+ }
1040
+ context.logger.info(`>> emitting stop with allowance ${allowance}`);
1041
+ context.emitter.emit("stop", allowance);
1042
+ });
1043
+ await flow(context);
1044
+ } catch (error) {
1045
+ const errorLocation = error instanceof Error && error.stack ? error.stack.split("\n")[1]?.trim() || "Unknown location" : "Unknown location";
1046
+ if (error instanceof ParamError) {
1047
+ context?.logger.error(`[params]: ${error.message} (${errorLocation})`);
1048
+ process.exitCode = 3;
1049
+ } else if (error instanceof InitError) {
1050
+ context?.logger.error(`[init]: ${error.message} (${errorLocation})`);
1051
+ process.exitCode = 4;
1052
+ } else {
1053
+ context?.logger.error(`[other] error:`, error, errorLocation);
1054
+ process.exitCode = 5;
1055
+ }
1056
+ } finally {
1057
+ if (context) {
1058
+ for (const fn of context.cleanupFunctions.reverse()) {
1059
+ try {
1060
+ await fn(context);
1061
+ } catch (error) {
1062
+ context.logger.warn("[cleanup] error in cleanup function:", error);
1063
+ }
1064
+ }
1065
+ const unusedArgs = context.args.getUnused();
1066
+ if (unusedArgs.length > 0) {
1067
+ context.logger.warn("Unused CLI args:", unusedArgs.join(", "));
1068
+ }
1069
+ }
1070
+ }
1071
+ }
1072
+ function setupContext(opts = {}) {
1073
+ return setup(opts);
1074
+ }
1075
+ export {
1076
+ init,
1077
+ setupContext
1078
+ };
1079
+ //# sourceMappingURL=init.js.map