@danielarndt0/brutils-cli 1.1.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/cli.js ADDED
@@ -0,0 +1,3346 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/create-program.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/services/zip/resolve-zip-output.ts
7
+ import path from "path";
8
+ function resolveZipOutputPath(sourcePath, explicitOut) {
9
+ if (explicitOut) {
10
+ return explicitOut;
11
+ }
12
+ const normalizedSource = path.resolve(sourcePath);
13
+ const parsed = path.parse(normalizedSource);
14
+ if (path.basename(normalizedSource) === ".") {
15
+ const cwd = process.cwd();
16
+ return path.join(cwd, `${path.basename(cwd)}.zip`);
17
+ }
18
+ if (parsed.ext) {
19
+ return path.join(parsed.dir, `${parsed.name}.zip`);
20
+ }
21
+ return path.join(parsed.dir, `${parsed.base}.zip`);
22
+ }
23
+
24
+ // src/services/zip/collect-zip-inputs.ts
25
+ import fs from "fs";
26
+ import path2 from "path";
27
+ import fg from "fast-glob";
28
+
29
+ // src/core/errors/brutils.error.ts
30
+ var BRUtilsError = class extends Error {
31
+ constructor(message) {
32
+ super(message);
33
+ this.name = "BRUtilsError";
34
+ }
35
+ };
36
+
37
+ // src/services/zip/collect-zip-inputs.ts
38
+ function normalizeEntryName(value) {
39
+ return value.replace(/\\/g, "/");
40
+ }
41
+ function isDirectory(sourcePath) {
42
+ return fs.statSync(sourcePath).isDirectory();
43
+ }
44
+ function isFile(sourcePath) {
45
+ return fs.statSync(sourcePath).isFile();
46
+ }
47
+ function collectZipInputs(sourcePath, outputPath, options = {}) {
48
+ const resolvedSource = path2.resolve(sourcePath);
49
+ if (!fs.existsSync(resolvedSource)) {
50
+ throw new BRUtilsError(`Source path does not exist: ${sourcePath}`);
51
+ }
52
+ if (isFile(resolvedSource)) {
53
+ return [
54
+ {
55
+ type: "file",
56
+ sourcePath: resolvedSource,
57
+ entryName: path2.basename(resolvedSource)
58
+ }
59
+ ];
60
+ }
61
+ if (!isDirectory(resolvedSource)) {
62
+ throw new BRUtilsError(`Unsupported source type: ${sourcePath}`);
63
+ }
64
+ const excludePatterns = options.exclude ?? [];
65
+ const outputAbsolutePath = path2.resolve(outputPath);
66
+ const rootEntryName = path2.basename(resolvedSource);
67
+ const entries = fg.sync(["**/*", "**/.*"], {
68
+ cwd: resolvedSource,
69
+ onlyFiles: false,
70
+ onlyDirectories: false,
71
+ dot: true,
72
+ followSymbolicLinks: options.followSymlinks ?? false,
73
+ unique: true,
74
+ ignore: excludePatterns,
75
+ markDirectories: true
76
+ });
77
+ if (entries.length === 0) {
78
+ return options.contentsOnly ? [] : [
79
+ {
80
+ type: "directory",
81
+ sourcePath: resolvedSource,
82
+ entryName: `${rootEntryName}/`
83
+ }
84
+ ];
85
+ }
86
+ return entries.map((entry) => {
87
+ const cleanedEntry = entry.endsWith("/") ? entry.slice(0, -1) : entry;
88
+ const absoluteEntryPath = path2.join(resolvedSource, cleanedEntry);
89
+ if (path2.resolve(absoluteEntryPath) === outputAbsolutePath) {
90
+ return null;
91
+ }
92
+ const relativeEntryName = options.contentsOnly ? cleanedEntry : path2.join(rootEntryName, cleanedEntry);
93
+ const entryType = entry.endsWith("/") ? "directory" : "file";
94
+ return {
95
+ type: entryType,
96
+ sourcePath: absoluteEntryPath,
97
+ entryName: normalizeEntryName(
98
+ entryType === "directory" ? `${relativeEntryName}/` : relativeEntryName
99
+ )
100
+ };
101
+ }).filter((entry) => entry !== null);
102
+ }
103
+
104
+ // src/services/zip/create-zip.ts
105
+ import fs2 from "fs";
106
+ import path3 from "path";
107
+ import archiver from "archiver";
108
+ function resolveCompressionLevel(level) {
109
+ const resolvedLevel = level ?? 9;
110
+ if (!Number.isInteger(resolvedLevel) || resolvedLevel < 0 || resolvedLevel > 9) {
111
+ throw new BRUtilsError(
112
+ "Compression level must be an integer between 0 and 9."
113
+ );
114
+ }
115
+ return resolvedLevel;
116
+ }
117
+ function getSourceType(sourcePath) {
118
+ return fs2.statSync(sourcePath).isDirectory() ? "directory" : "file";
119
+ }
120
+ function ensureOutputCanBeWritten(outputPath, force = false) {
121
+ if (fs2.existsSync(outputPath) && !force) {
122
+ throw new BRUtilsError(
123
+ `Output file already exists: ${outputPath}. Use --force to overwrite it.`
124
+ );
125
+ }
126
+ fs2.mkdirSync(path3.dirname(outputPath), { recursive: true });
127
+ }
128
+ function createZipExecutionPlan(sourcePath, positionalOut, options = {}) {
129
+ if (positionalOut && options.out) {
130
+ throw new BRUtilsError("Use either positional [out] or --out, not both.");
131
+ }
132
+ const resolvedOutputPath = path3.resolve(
133
+ resolveZipOutputPath(sourcePath, positionalOut ?? options.out)
134
+ );
135
+ return {
136
+ sourcePath: path3.resolve(sourcePath),
137
+ outputPath: resolvedOutputPath,
138
+ sourceType: getSourceType(path3.resolve(sourcePath)),
139
+ exclude: options.exclude ?? [],
140
+ contentsOnly: options.contentsOnly ?? false,
141
+ level: resolveCompressionLevel(options.level),
142
+ followSymlinks: options.followSymlinks ?? false,
143
+ store: options.store ?? false
144
+ };
145
+ }
146
+ async function createZip(sourcePath, positionalOut, options = {}) {
147
+ const plan = createZipExecutionPlan(sourcePath, positionalOut, options);
148
+ if (options.dryRun) {
149
+ return plan;
150
+ }
151
+ ensureOutputCanBeWritten(plan.outputPath, options.force);
152
+ const outputStream = fs2.createWriteStream(plan.outputPath);
153
+ const archive = archiver("zip", {
154
+ zlib: { level: plan.store ? 0 : plan.level },
155
+ store: plan.store
156
+ });
157
+ await new Promise((resolve, reject) => {
158
+ outputStream.on("close", () => resolve());
159
+ archive.on("error", (error) => reject(error));
160
+ archive.pipe(outputStream);
161
+ const inputs = collectZipInputs(plan.sourcePath, plan.outputPath, options);
162
+ for (const input of inputs) {
163
+ if (options.verbose && !options.quiet) {
164
+ console.log(`[zip] adding ${input.entryName}`);
165
+ }
166
+ if (input.type === "directory") {
167
+ archive.append("", { name: input.entryName });
168
+ } else {
169
+ archive.file(input.sourcePath, { name: input.entryName });
170
+ }
171
+ }
172
+ archive.finalize().catch(reject);
173
+ });
174
+ return plan;
175
+ }
176
+
177
+ // src/services/zip/list-zip.ts
178
+ import path5 from "path";
179
+
180
+ // src/services/archive/zip-archive.ts
181
+ import fs3 from "fs";
182
+ import path4 from "path";
183
+ import { Writable } from "stream";
184
+ import { pipeline } from "stream/promises";
185
+ import yauzl from "yauzl";
186
+
187
+ // src/core/utils/glob.ts
188
+ import * as minimatchModule from "minimatch";
189
+ function resolveMinimatch() {
190
+ const moduleValue = minimatchModule;
191
+ if (typeof moduleValue.minimatch === "function") {
192
+ return moduleValue.minimatch;
193
+ }
194
+ if (typeof moduleValue.default === "function") {
195
+ return moduleValue.default;
196
+ }
197
+ if (typeof moduleValue === "function") {
198
+ return moduleValue;
199
+ }
200
+ throw new Error("Could not resolve minimatch implementation.");
201
+ }
202
+ var minimatch = resolveMinimatch();
203
+ function matchesGlob(input, pattern) {
204
+ return minimatch(input, pattern, { dot: true });
205
+ }
206
+
207
+ // src/services/archive/zip-archive.ts
208
+ function normalizeEntryPath(entryPath) {
209
+ return entryPath.replace(/\\/g, "/");
210
+ }
211
+ function ensureZipSourceIsValid(sourcePath) {
212
+ if (!fs3.existsSync(sourcePath)) {
213
+ throw new BRUtilsError(`Zip file does not exist: ${sourcePath}`);
214
+ }
215
+ if (!fs3.statSync(sourcePath).isFile()) {
216
+ throw new BRUtilsError(`Source is not a file: ${sourcePath}`);
217
+ }
218
+ if (path4.extname(sourcePath).toLowerCase() !== ".zip") {
219
+ throw new BRUtilsError("Only .zip files are supported.");
220
+ }
221
+ }
222
+ function openZipFile(sourcePath) {
223
+ ensureZipSourceIsValid(sourcePath);
224
+ return new Promise((resolve, reject) => {
225
+ yauzl.open(
226
+ sourcePath,
227
+ {
228
+ lazyEntries: true,
229
+ decodeStrings: true,
230
+ validateEntrySizes: true
231
+ },
232
+ (error, zipFile) => {
233
+ if (error) {
234
+ reject(error);
235
+ return;
236
+ }
237
+ if (!zipFile) {
238
+ reject(new BRUtilsError("Could not open zip file."));
239
+ return;
240
+ }
241
+ resolve(zipFile);
242
+ }
243
+ );
244
+ });
245
+ }
246
+ function shouldIncludeEntry(entryPath, match) {
247
+ if (!match) {
248
+ return true;
249
+ }
250
+ return matchesGlob(normalizeEntryPath(entryPath), match);
251
+ }
252
+ async function listZipArchiveEntries(sourcePath, options = {}) {
253
+ const zipFile = await openZipFile(sourcePath);
254
+ return new Promise((resolve, reject) => {
255
+ const entries = [];
256
+ zipFile.on("entry", (entry) => {
257
+ const entryPath = normalizeEntryPath(entry.fileName);
258
+ if (shouldIncludeEntry(entryPath, options.match)) {
259
+ entries.push({
260
+ path: entryPath,
261
+ type: entryPath.endsWith("/") ? "directory" : "file",
262
+ compressedSize: entry.compressedSize,
263
+ uncompressedSize: entry.uncompressedSize
264
+ });
265
+ }
266
+ zipFile.readEntry();
267
+ });
268
+ zipFile.once("end", () => {
269
+ zipFile.close();
270
+ resolve(entries);
271
+ });
272
+ zipFile.once("error", (error) => {
273
+ zipFile.close();
274
+ reject(error);
275
+ });
276
+ zipFile.readEntry();
277
+ });
278
+ }
279
+ async function drainEntryStream(zipFile, entry) {
280
+ await new Promise((resolve, reject) => {
281
+ zipFile.openReadStream(entry, async (error, stream) => {
282
+ if (error) {
283
+ reject(error);
284
+ return;
285
+ }
286
+ if (!stream) {
287
+ reject(
288
+ new BRUtilsError(`Could not read archive entry: ${entry.fileName}`)
289
+ );
290
+ return;
291
+ }
292
+ try {
293
+ await pipeline(
294
+ stream,
295
+ new Writable({
296
+ write(_chunk, _encoding, callback) {
297
+ callback();
298
+ }
299
+ })
300
+ );
301
+ resolve();
302
+ } catch (streamError) {
303
+ reject(streamError);
304
+ }
305
+ });
306
+ });
307
+ }
308
+ async function testZipArchive(sourcePath, options = {}) {
309
+ const zipFile = await openZipFile(sourcePath);
310
+ let testedEntries = 0;
311
+ return new Promise((resolve, reject) => {
312
+ zipFile.on("entry", (entry) => {
313
+ const entryPath = normalizeEntryPath(entry.fileName);
314
+ void (async () => {
315
+ try {
316
+ if (!shouldIncludeEntry(entryPath, options.match) || entryPath.endsWith("/")) {
317
+ zipFile.readEntry();
318
+ return;
319
+ }
320
+ await drainEntryStream(zipFile, entry);
321
+ testedEntries += 1;
322
+ zipFile.readEntry();
323
+ } catch (error) {
324
+ zipFile.close();
325
+ reject(error);
326
+ }
327
+ })();
328
+ });
329
+ zipFile.once("end", () => {
330
+ zipFile.close();
331
+ resolve({
332
+ sourcePath: path4.resolve(sourcePath),
333
+ testedEntries,
334
+ ok: true
335
+ });
336
+ });
337
+ zipFile.once("error", (error) => {
338
+ zipFile.close();
339
+ reject(error);
340
+ });
341
+ zipFile.readEntry();
342
+ });
343
+ }
344
+
345
+ // src/services/zip/list-zip.ts
346
+ async function listZip(sourcePath, match) {
347
+ return listZipArchiveEntries(
348
+ path5.resolve(sourcePath),
349
+ match ? { match } : {}
350
+ );
351
+ }
352
+
353
+ // src/services/zip/test-zip.ts
354
+ import path6 from "path";
355
+ async function testZip(sourcePath, match) {
356
+ return testZipArchive(path6.resolve(sourcePath), match ? { match } : {});
357
+ }
358
+
359
+ // src/services/unzip/resolve-unzip-output.ts
360
+ import path7 from "path";
361
+ function resolveUnzipOutputPath(sourcePath, explicitOut) {
362
+ if (explicitOut) {
363
+ return explicitOut;
364
+ }
365
+ const resolvedSource = path7.resolve(sourcePath);
366
+ const parsed = path7.parse(resolvedSource);
367
+ return path7.join(parsed.dir, parsed.name);
368
+ }
369
+
370
+ // src/services/unzip/extract-zip.ts
371
+ import fs4 from "fs";
372
+ import path8 from "path";
373
+ import { pipeline as pipeline2 } from "stream/promises";
374
+ import yauzl2 from "yauzl";
375
+ function ensureOutputCanBeExtracted(outputDir, force = false) {
376
+ if (fs4.existsSync(outputDir) && !force) {
377
+ throw new BRUtilsError(
378
+ `Output directory already exists: ${outputDir}. Use --force to overwrite it.`
379
+ );
380
+ }
381
+ if (fs4.existsSync(outputDir) && force) {
382
+ fs4.rmSync(outputDir, { recursive: true, force: true });
383
+ }
384
+ fs4.mkdirSync(outputDir, { recursive: true });
385
+ }
386
+ function normalizeEntryPath2(entryPath) {
387
+ return entryPath.replace(/\\/g, "/");
388
+ }
389
+ function shouldIncludeEntry2(entryPath, match) {
390
+ if (!match) {
391
+ return true;
392
+ }
393
+ return matchesGlob(normalizeEntryPath2(entryPath), match);
394
+ }
395
+ function resolveTargetRelativePath(entryPath, flat) {
396
+ if (!flat) {
397
+ return normalizeEntryPath2(entryPath);
398
+ }
399
+ return path8.posix.basename(normalizeEntryPath2(entryPath));
400
+ }
401
+ function resolveSafeDestination(outputDir, relativePath) {
402
+ const destination = path8.resolve(outputDir, relativePath);
403
+ const resolvedOutputDir = path8.resolve(outputDir);
404
+ const outputPrefix = `${resolvedOutputDir}${path8.sep}`;
405
+ if (destination !== resolvedOutputDir && !destination.startsWith(outputPrefix)) {
406
+ throw new BRUtilsError(`Unsafe zip entry path detected: ${relativePath}`);
407
+ }
408
+ return destination;
409
+ }
410
+ function openZipFile2(sourcePath) {
411
+ ensureZipSourceIsValid(sourcePath);
412
+ return new Promise((resolve, reject) => {
413
+ yauzl2.open(
414
+ sourcePath,
415
+ {
416
+ lazyEntries: true,
417
+ decodeStrings: true,
418
+ validateEntrySizes: true
419
+ },
420
+ (error, zipFile) => {
421
+ if (error) {
422
+ reject(error);
423
+ return;
424
+ }
425
+ if (!zipFile) {
426
+ reject(new BRUtilsError("Could not open zip file."));
427
+ return;
428
+ }
429
+ resolve(zipFile);
430
+ }
431
+ );
432
+ });
433
+ }
434
+ function createUnzipExecutionPlan(sourcePath, positionalOut, options = {}) {
435
+ if (positionalOut && options.out) {
436
+ throw new BRUtilsError("Use either positional [out] or --out, not both.");
437
+ }
438
+ const resolvedSourcePath = path8.resolve(sourcePath);
439
+ const resolvedOutputDir = path8.resolve(
440
+ resolveUnzipOutputPath(sourcePath, positionalOut ?? options.out)
441
+ );
442
+ return {
443
+ sourcePath: resolvedSourcePath,
444
+ outputDir: resolvedOutputDir,
445
+ flat: options.flat ?? false,
446
+ ...options.match ? { match: options.match } : {}
447
+ };
448
+ }
449
+ async function extractZipFile(sourcePath, positionalOut, options = {}) {
450
+ const plan = createUnzipExecutionPlan(sourcePath, positionalOut, options);
451
+ ensureZipSourceIsValid(plan.sourcePath);
452
+ if (options.dryRun) {
453
+ return plan;
454
+ }
455
+ ensureOutputCanBeExtracted(plan.outputDir, options.force);
456
+ if (options.verbose && !options.quiet) {
457
+ console.log(`[unzip] extracting ${plan.sourcePath} -> ${plan.outputDir}`);
458
+ }
459
+ const zipFile = await openZipFile2(plan.sourcePath);
460
+ const flatTargets = /* @__PURE__ */ new Set();
461
+ await new Promise((resolve, reject) => {
462
+ zipFile.on("entry", (entry) => {
463
+ const entryPath = normalizeEntryPath2(entry.fileName);
464
+ void (async () => {
465
+ try {
466
+ if (!shouldIncludeEntry2(entryPath, options.match)) {
467
+ zipFile.readEntry();
468
+ return;
469
+ }
470
+ const isDirectory2 = entryPath.endsWith("/");
471
+ const targetRelativePath = resolveTargetRelativePath(
472
+ entryPath,
473
+ options.flat ?? false
474
+ );
475
+ if (!targetRelativePath) {
476
+ zipFile.readEntry();
477
+ return;
478
+ }
479
+ if ((options.flat ?? false) && flatTargets.has(targetRelativePath) && !isDirectory2) {
480
+ throw new BRUtilsError(
481
+ `Flat extraction would overwrite a file more than once: ${targetRelativePath}`
482
+ );
483
+ }
484
+ if ((options.flat ?? false) && !isDirectory2) {
485
+ flatTargets.add(targetRelativePath);
486
+ }
487
+ const destinationPath = resolveSafeDestination(
488
+ plan.outputDir,
489
+ targetRelativePath
490
+ );
491
+ if (options.verbose && !options.quiet) {
492
+ console.log(`[unzip] extracting ${entryPath}`);
493
+ }
494
+ if (isDirectory2) {
495
+ if (!(options.flat ?? false)) {
496
+ fs4.mkdirSync(destinationPath, { recursive: true });
497
+ }
498
+ zipFile.readEntry();
499
+ return;
500
+ }
501
+ fs4.mkdirSync(path8.dirname(destinationPath), { recursive: true });
502
+ await new Promise((entryResolve, entryReject) => {
503
+ zipFile.openReadStream(entry, async (error, stream) => {
504
+ if (error) {
505
+ entryReject(error);
506
+ return;
507
+ }
508
+ if (!stream) {
509
+ entryReject(
510
+ new BRUtilsError(
511
+ `Could not read archive entry: ${entry.fileName}`
512
+ )
513
+ );
514
+ return;
515
+ }
516
+ try {
517
+ await pipeline2(stream, fs4.createWriteStream(destinationPath));
518
+ entryResolve();
519
+ } catch (streamError) {
520
+ entryReject(streamError);
521
+ }
522
+ });
523
+ });
524
+ zipFile.readEntry();
525
+ } catch (error) {
526
+ zipFile.close();
527
+ reject(error);
528
+ }
529
+ })();
530
+ });
531
+ zipFile.once("end", () => {
532
+ zipFile.close();
533
+ resolve();
534
+ });
535
+ zipFile.once("error", (error) => {
536
+ zipFile.close();
537
+ reject(error);
538
+ });
539
+ zipFile.readEntry();
540
+ });
541
+ return plan;
542
+ }
543
+
544
+ // src/services/unzip/list-unzip.ts
545
+ import path9 from "path";
546
+ async function listUnzip(sourcePath, match) {
547
+ return listZipArchiveEntries(
548
+ path9.resolve(sourcePath),
549
+ match ? { match } : {}
550
+ );
551
+ }
552
+
553
+ // src/services/unzip/test-unzip.ts
554
+ import path10 from "path";
555
+ async function testUnzip(sourcePath, match) {
556
+ return testZipArchive(path10.resolve(sourcePath), match ? { match } : {});
557
+ }
558
+
559
+ // src/cli/ui/theme.ts
560
+ var colorsEnabled = process.stdout.isTTY && !process.argv.includes("--no-color") && process.env.NO_COLOR === void 0;
561
+ function paint(code, value) {
562
+ if (!colorsEnabled) {
563
+ return value;
564
+ }
565
+ return `\x1B[${code}m${value}\x1B[0m`;
566
+ }
567
+ var theme = {
568
+ muted: (value) => paint(90, value),
569
+ red: (value) => paint(31, value),
570
+ green: (value) => paint(32, value),
571
+ yellow: (value) => paint(33, value),
572
+ blue: (value) => paint(34, value),
573
+ magenta: (value) => paint(35, value),
574
+ cyan: (value) => paint(36, value),
575
+ bold: (value) => paint(1, value),
576
+ section: (value) => paint(36, paint(1, value)),
577
+ command: (value) => paint(36, value),
578
+ flag: (value) => paint(33, value),
579
+ value: (value) => paint(32, value),
580
+ errorLabel: (value) => paint(31, paint(1, value)),
581
+ successLabel: (value) => paint(32, paint(1, value))
582
+ };
583
+
584
+ // src/cli/shared/help.ts
585
+ function examples(lines) {
586
+ return `
587
+ ${theme.section("Examples")}
588
+ ${lines.map((line) => ` ${theme.command(line)}`).join("\n")}
589
+ `;
590
+ }
591
+ function rootFooter() {
592
+ return `
593
+ ${theme.section("Quick start")}
594
+ ${theme.command("brutils <command> --help")}
595
+ ${theme.command("brutils <command> <action> --help")}
596
+
597
+ ${theme.section("Examples")}
598
+ ${theme.command("brutils cpf generate --formatted")}
599
+ ${theme.command('brutils str slug --text "Hello Cool World"')}
600
+ ${theme.command("brutils json format --file ./config.json --sort-keys")}
601
+ ${theme.command("brutils hash sha256 --text hello")}
602
+ ${theme.command("brutils id token --length 24 --charset base64url")}
603
+ ${theme.command("brutils date diff --from 2024-01-01T00:00:00Z --to 2024-01-03T00:00:00Z --unit days")}
604
+ ${theme.command("brutils random-number int --min 1 --max 60 --count 6 --unique --sorted")}
605
+ ${theme.command("brutils zip create ./folder --out ./backup.zip --force")}
606
+
607
+ ${theme.section("Tips")}
608
+ ${theme.muted("The official interface is the brutils command.")}
609
+ ${theme.muted("Use --no-color if you want plain output.")}
610
+ ${theme.muted("Use module-specific --help to see focused examples.")}
611
+ `;
612
+ }
613
+
614
+ // src/cli/shared/parsers.ts
615
+ import { InvalidArgumentError } from "commander";
616
+
617
+ // src/cli/shared/constants.ts
618
+ var BRAZILIAN_STATES = [
619
+ "AC",
620
+ "AL",
621
+ "AM",
622
+ "AP",
623
+ "BA",
624
+ "CE",
625
+ "DF",
626
+ "ES",
627
+ "GO",
628
+ "MA",
629
+ "MG",
630
+ "MS",
631
+ "MT",
632
+ "PA",
633
+ "PB",
634
+ "PE",
635
+ "PI",
636
+ "PR",
637
+ "RJ",
638
+ "RN",
639
+ "RO",
640
+ "RR",
641
+ "RS",
642
+ "SC",
643
+ "SE",
644
+ "SP",
645
+ "TO"
646
+ ];
647
+ var CARD_BRANDS = ["visa", "mastercard", "amex", "elo"];
648
+ var RANDOM_OUTPUT_FORMATS = ["plain", "json", "csv"];
649
+ var STRING_CASE_STYLES = [
650
+ "camel",
651
+ "snake",
652
+ "kebab",
653
+ "pascal",
654
+ "constant",
655
+ "title"
656
+ ];
657
+ var STRING_CODEC_MODES = ["encode", "decode"];
658
+ var STRING_PAD_SIDES = ["left", "right", "both"];
659
+ var CHARSET_NAMES = [
660
+ "alnum",
661
+ "alpha",
662
+ "numeric",
663
+ "hex",
664
+ "base64url",
665
+ "lower",
666
+ "upper"
667
+ ];
668
+ var DATE_DIFF_UNITS = ["seconds", "minutes", "hours", "days"];
669
+
670
+ // src/cli/shared/parsers.ts
671
+ function parseInteger(value) {
672
+ const parsed = Number(value);
673
+ if (!Number.isInteger(parsed)) {
674
+ throw new InvalidArgumentError(`Expected an integer, received "${value}".`);
675
+ }
676
+ return parsed;
677
+ }
678
+ function parsePositiveInteger(value) {
679
+ const parsed = parseInteger(value);
680
+ if (parsed < 1) {
681
+ throw new InvalidArgumentError(
682
+ `Expected a positive integer, received "${value}".`
683
+ );
684
+ }
685
+ return parsed;
686
+ }
687
+ function parseNumber(value) {
688
+ const parsed = Number(value);
689
+ if (!Number.isFinite(parsed)) {
690
+ throw new InvalidArgumentError(
691
+ `Expected a numeric value, received "${value}".`
692
+ );
693
+ }
694
+ return parsed;
695
+ }
696
+ function parseState(value) {
697
+ const normalized = value.toUpperCase();
698
+ if (!BRAZILIAN_STATES.includes(normalized)) {
699
+ throw new InvalidArgumentError(
700
+ `Invalid state "${value}". Use one of: ${BRAZILIAN_STATES.join(", ")}.`
701
+ );
702
+ }
703
+ return normalized;
704
+ }
705
+ function parseCardBrand(value) {
706
+ const normalized = value.toLowerCase();
707
+ if (!CARD_BRANDS.includes(normalized)) {
708
+ throw new InvalidArgumentError(
709
+ `Invalid brand "${value}". Use one of: ${CARD_BRANDS.join(", ")}.`
710
+ );
711
+ }
712
+ return normalized;
713
+ }
714
+ function parseRandomFormat(value) {
715
+ const normalized = value.toLowerCase();
716
+ if (!RANDOM_OUTPUT_FORMATS.includes(normalized)) {
717
+ throw new InvalidArgumentError(
718
+ `Invalid output format "${value}". Use one of: ${RANDOM_OUTPUT_FORMATS.join(", ")}.`
719
+ );
720
+ }
721
+ return normalized;
722
+ }
723
+ function parseStringCaseStyle(value) {
724
+ const normalized = value.toLowerCase();
725
+ if (!STRING_CASE_STYLES.includes(normalized)) {
726
+ throw new InvalidArgumentError(
727
+ `Invalid string case style "${value}". Use one of: ${STRING_CASE_STYLES.join(", ")}.`
728
+ );
729
+ }
730
+ return normalized;
731
+ }
732
+ function parseStringCodecMode(value) {
733
+ const normalized = value.toLowerCase();
734
+ if (!STRING_CODEC_MODES.includes(normalized)) {
735
+ throw new InvalidArgumentError(
736
+ `Invalid codec mode "${value}". Use one of: ${STRING_CODEC_MODES.join(", ")}.`
737
+ );
738
+ }
739
+ return normalized;
740
+ }
741
+ function parseStringPadSide(value) {
742
+ const normalized = value.toLowerCase();
743
+ if (!STRING_PAD_SIDES.includes(normalized)) {
744
+ throw new InvalidArgumentError(
745
+ `Invalid pad side "${value}". Use one of: ${STRING_PAD_SIDES.join(", ")}.`
746
+ );
747
+ }
748
+ return normalized;
749
+ }
750
+ function parseCharsetName(value) {
751
+ const normalized = value.toLowerCase();
752
+ if (!CHARSET_NAMES.includes(normalized)) {
753
+ throw new InvalidArgumentError(
754
+ `Invalid charset "${value}". Use one of: ${CHARSET_NAMES.join(", ")}.`
755
+ );
756
+ }
757
+ return normalized;
758
+ }
759
+ function parseDateDiffUnit(value) {
760
+ const normalized = value.toLowerCase();
761
+ if (!DATE_DIFF_UNITS.includes(normalized)) {
762
+ throw new InvalidArgumentError(
763
+ `Invalid diff unit "${value}". Use one of: ${DATE_DIFF_UNITS.join(", ")}.`
764
+ );
765
+ }
766
+ return normalized;
767
+ }
768
+
769
+ // src/cli/ui/output.ts
770
+ function configureProgramUi(program) {
771
+ program.configureOutput({
772
+ writeErr: (str) => process.stderr.write(str),
773
+ outputError: (str, write) => {
774
+ write(theme.red(str));
775
+ }
776
+ });
777
+ program.configureHelp({
778
+ subcommandTerm: (cmd) => theme.command(cmd.name()),
779
+ optionTerm: (option) => theme.flag(option.flags)
780
+ });
781
+ }
782
+ function printValue(value) {
783
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
784
+ console.log(value);
785
+ return;
786
+ }
787
+ if (Array.isArray(value)) {
788
+ if (value.every(
789
+ (item) => typeof item === "string" || typeof item === "number"
790
+ )) {
791
+ console.log(value.join("\n"));
792
+ return;
793
+ }
794
+ }
795
+ console.log(JSON.stringify(value, null, 2));
796
+ }
797
+ function printRandomValues(values, format) {
798
+ if (format === "json") {
799
+ console.log(JSON.stringify(values, null, 2));
800
+ return;
801
+ }
802
+ if (format === "csv") {
803
+ console.log(values.join(","));
804
+ return;
805
+ }
806
+ console.log(values.join("\n"));
807
+ }
808
+ function handleCliError(error) {
809
+ const message = error instanceof Error ? error.message : String(error);
810
+ console.error(`${theme.errorLabel("error")} ${message}`);
811
+ process.exit(1);
812
+ }
813
+
814
+ // src/cli/commands/register-archive.ts
815
+ function registerArchiveCommands(program) {
816
+ const zip = program.command("zip").description("Create zip archives and inspect them.").addHelpText(
817
+ "after",
818
+ examples([
819
+ "brutils zip create ./folder",
820
+ "brutils zip create ./folder ./backup.zip",
821
+ "brutils zip create ./folder --out ./backup.zip --force",
822
+ "brutils zip create ./folder --contents-only --exclude node_modules --exclude dist",
823
+ 'brutils zip list ./backup.zip --match "**/*.txt"',
824
+ "brutils zip test ./backup.zip"
825
+ ])
826
+ );
827
+ zip.command("create").alias("run").argument("<source>", "File or folder to archive.").argument("[out]", "Optional output .zip file path.").description("Create a zip archive from a source path.").option("-o, --out <path>", "Explicit output file path.").option(
828
+ "-l, --level <number>",
829
+ "Compression level from 0 to 9.",
830
+ parseInteger
831
+ ).option(
832
+ "-x, --exclude <glob...>",
833
+ "Glob patterns to exclude when zipping folder contents."
834
+ ).option(
835
+ "--contents-only",
836
+ "Zip only the folder contents instead of the folder root."
837
+ ).option("-f, --force", "Overwrite output if it already exists.").option(
838
+ "--dry-run",
839
+ "Print the execution plan without writing the archive."
840
+ ).option("-v, --verbose", "Show verbose archive creation logs.").option("-q, --quiet", "Suppress non-error output.").option(
841
+ "--follow-symlinks",
842
+ "Follow symbolic links while collecting inputs."
843
+ ).option("--store", "Store files without compression.").action(
844
+ async (source, out, options) => {
845
+ const plan = await createZip(source, out, options);
846
+ if (!options.quiet) {
847
+ printValue(plan);
848
+ }
849
+ }
850
+ );
851
+ zip.command("list").argument("<source>", "Existing .zip file to inspect.").description("List archive contents without extracting.").option("--match <pattern>", "Filter which archive entries are included.").option("-q, --quiet", "Suppress non-error output.").action(
852
+ async (source, options) => {
853
+ const result = await listZip(source, options.match);
854
+ if (!options.quiet) {
855
+ printValue(result);
856
+ }
857
+ }
858
+ );
859
+ zip.command("test").argument("<source>", "Existing .zip file to validate.").description("Test archive readability without extracting files.").option("--match <pattern>", "Filter which archive entries are included.").option("-q, --quiet", "Suppress non-error output.").action(
860
+ async (source, options) => {
861
+ const result = await testZip(source, options.match);
862
+ if (!options.quiet) {
863
+ printValue(result);
864
+ }
865
+ }
866
+ );
867
+ const unzip = program.command("unzip").description("Extract zip archives and inspect them.").addHelpText(
868
+ "after",
869
+ examples([
870
+ "brutils unzip extract ./backup.zip",
871
+ "brutils unzip extract ./backup.zip ./output",
872
+ "brutils unzip extract ./backup.zip --out ./output --force",
873
+ 'brutils unzip extract ./backup.zip --flat --match "**/*.txt"',
874
+ "brutils unzip list ./backup.zip",
875
+ "brutils unzip test ./backup.zip"
876
+ ])
877
+ );
878
+ unzip.command("extract").alias("run").argument("<source>", "Existing .zip file to extract.").argument("[out]", "Optional destination directory.").description("Extract a zip archive.").option("-o, --out <path>", "Explicit output directory.").option(
879
+ "-f, --force",
880
+ "Overwrite the target directory if it already exists."
881
+ ).option("--dry-run", "Print the extraction plan without writing files.").option("-v, --verbose", "Show verbose extraction logs.").option("-q, --quiet", "Suppress non-error output.").option("--flat", "Extract files without preserving nested folders.").option("--match <pattern>", "Filter which archive entries are extracted.").action(
882
+ async (source, out, options) => {
883
+ const plan = await extractZipFile(source, out, options);
884
+ if (!options.quiet) {
885
+ printValue(plan);
886
+ }
887
+ }
888
+ );
889
+ unzip.command("list").argument("<source>", "Existing .zip file to inspect.").description("List archive contents without extracting.").option("--match <pattern>", "Filter which archive entries are included.").option("-q, --quiet", "Suppress non-error output.").action(
890
+ async (source, options) => {
891
+ const result = await listUnzip(source, options.match);
892
+ if (!options.quiet) {
893
+ printValue(result);
894
+ }
895
+ }
896
+ );
897
+ unzip.command("test").argument("<source>", "Existing .zip file to validate.").description("Test archive readability without extracting files.").option("--match <pattern>", "Filter which archive entries are included.").option("-q, --quiet", "Suppress non-error output.").action(
898
+ async (source, options) => {
899
+ const result = await testUnzip(source, options.match);
900
+ if (!options.quiet) {
901
+ printValue(result);
902
+ }
903
+ }
904
+ );
905
+ }
906
+
907
+ // src/core/utils/digits.ts
908
+ function onlyDigits(value) {
909
+ return value.replace(/\D/g, "");
910
+ }
911
+ function allDigitsEqual(value) {
912
+ return /^(\d)\1+$/.test(value);
913
+ }
914
+
915
+ // src/services/cep/cep.shared.ts
916
+ var CEP_STATE_LEADING_DIGIT_MAP = {
917
+ AC: [6],
918
+ AL: [5],
919
+ AM: [6],
920
+ AP: [6],
921
+ BA: [4],
922
+ CE: [6],
923
+ DF: [7],
924
+ ES: [2],
925
+ GO: [7],
926
+ MA: [6],
927
+ MG: [3],
928
+ MS: [7],
929
+ MT: [7],
930
+ PA: [6],
931
+ PB: [5],
932
+ PE: [5],
933
+ PI: [6],
934
+ PR: [8],
935
+ RJ: [2],
936
+ RN: [5],
937
+ RO: [7],
938
+ RR: [6],
939
+ RS: [9],
940
+ SC: [8],
941
+ SE: [4],
942
+ SP: [0, 1],
943
+ TO: [7]
944
+ };
945
+ function normalizeCEP(value) {
946
+ return onlyDigits(value);
947
+ }
948
+ function stripCEP(value) {
949
+ return normalizeCEP(value);
950
+ }
951
+ function ensureCEPLength(value) {
952
+ const digits = normalizeCEP(value);
953
+ if (digits.length !== 8)
954
+ throw new BRUtilsError("CEP must contain exactly 8 digits.");
955
+ return digits;
956
+ }
957
+ function resolveCEPLeadingDigits(state) {
958
+ return state ? CEP_STATE_LEADING_DIGIT_MAP[state] : void 0;
959
+ }
960
+
961
+ // src/core/utils/format.ts
962
+ function formatCPF(value) {
963
+ return value.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4");
964
+ }
965
+ function formatCNPJ(value) {
966
+ return value.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, "$1.$2.$3/$4-$5");
967
+ }
968
+ function formatCEP(value) {
969
+ return value.replace(/(\d{5})(\d{3})/, "$1-$2");
970
+ }
971
+
972
+ // src/services/cep/cep.formatter.ts
973
+ function formatCEPValue(value) {
974
+ return formatCEP(ensureCEPLength(value));
975
+ }
976
+
977
+ // src/core/utils/mask.ts
978
+ function applyDigitMask(value, pattern) {
979
+ let index = 0;
980
+ let result = "";
981
+ for (const token of pattern) {
982
+ if (token === "*") {
983
+ if (value[index] === void 0)
984
+ throw new BRUtilsError(
985
+ "Mask pattern consumes more digits than the input provides."
986
+ );
987
+ result += "*";
988
+ index += 1;
989
+ continue;
990
+ }
991
+ if (token === "#" || /\d/.test(token)) {
992
+ const digit = value[index];
993
+ if (digit === void 0)
994
+ throw new BRUtilsError(
995
+ "Mask pattern consumes more digits than the input provides."
996
+ );
997
+ result += digit;
998
+ index += 1;
999
+ continue;
1000
+ }
1001
+ result += token;
1002
+ }
1003
+ if (index != value.length)
1004
+ throw new BRUtilsError(
1005
+ `Mask pattern consumed ${index} digits, but the input has ${value.length}.`
1006
+ );
1007
+ return result;
1008
+ }
1009
+
1010
+ // src/services/cep/cep.masker.ts
1011
+ var DEFAULT_CEP_MASK_PATTERN = "*****-###";
1012
+ function maskCEP(value, options = {}) {
1013
+ return applyDigitMask(
1014
+ ensureCEPLength(value),
1015
+ options.pattern ?? DEFAULT_CEP_MASK_PATTERN
1016
+ );
1017
+ }
1018
+
1019
+ // src/core/utils/random.ts
1020
+ function randomDigit() {
1021
+ return Math.floor(Math.random() * 10);
1022
+ }
1023
+ function randomDigits(length) {
1024
+ return Array.from({ length }, () => randomDigit());
1025
+ }
1026
+
1027
+ // src/services/cep/cep.generator.ts
1028
+ function generateCEP(options = {}) {
1029
+ const stateLeadingDigits = resolveCEPLeadingDigits(options.state);
1030
+ const firstDigit = stateLeadingDigits ? stateLeadingDigits[Math.floor(Math.random() * stateLeadingDigits.length)] : randomDigit();
1031
+ const digits = [firstDigit, ...randomDigits(7)].join("");
1032
+ return options.formatted ? formatCEP(digits) : digits;
1033
+ }
1034
+ function generateCEPBatch(options) {
1035
+ const count = options.count;
1036
+ if (!Number.isInteger(count) || count <= 0)
1037
+ throw new BRUtilsError("Count must be a positive integer.");
1038
+ return Array.from(
1039
+ { length: count },
1040
+ () => generateCEP({ formatted: options.formatted, state: options.state })
1041
+ );
1042
+ }
1043
+
1044
+ // src/services/cep/cep.validator.ts
1045
+ function validateCEP(value, options = {}) {
1046
+ const digits = normalizeCEP(value);
1047
+ const hasValidLength = digits.length === 8;
1048
+ const passesStrictRules = !options.strict || !allDigitsEqual(digits);
1049
+ return {
1050
+ isValid: hasValidLength && passesStrictRules,
1051
+ value: digits,
1052
+ formatted: hasValidLength ? formatCEP(digits) : void 0
1053
+ };
1054
+ }
1055
+
1056
+ // src/services/cnpj/cnpj.shared.ts
1057
+ function normalizeCNPJ(value) {
1058
+ return onlyDigits(value);
1059
+ }
1060
+ function stripCNPJ(value) {
1061
+ return normalizeCNPJ(value);
1062
+ }
1063
+ function ensureCNPJLength(value) {
1064
+ const digits = normalizeCNPJ(value);
1065
+ if (digits.length !== 14)
1066
+ throw new BRUtilsError("CNPJ must contain exactly 14 digits.");
1067
+ return digits;
1068
+ }
1069
+ function normalizeCNPJBranch(value) {
1070
+ const branch = normalizeCNPJ(value ?? "0001");
1071
+ if (!/^\d{1,4}$/.test(branch))
1072
+ throw new BRUtilsError("Branch must contain between 1 and 4 digits.");
1073
+ return branch.padStart(4, "0");
1074
+ }
1075
+ function calculateCNPJCheckDigit(digits) {
1076
+ const weights = digits.length === 12 ? [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] : [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
1077
+ const sum = digits.reduce(
1078
+ (acc, digit, index) => acc + digit * weights[index],
1079
+ 0
1080
+ );
1081
+ const remainder = sum % 11;
1082
+ return remainder < 2 ? 0 : 11 - remainder;
1083
+ }
1084
+ function resolveCNPJBranch(value) {
1085
+ const digits = normalizeCNPJ(value);
1086
+ return digits.length < 12 ? void 0 : digits.slice(8, 12);
1087
+ }
1088
+
1089
+ // src/services/cnpj/cnpj.formatter.ts
1090
+ function formatCNPJValue(value) {
1091
+ return formatCNPJ(ensureCNPJLength(value));
1092
+ }
1093
+
1094
+ // src/services/cnpj/cnpj.masker.ts
1095
+ var DEFAULT_CNPJ_MASK_PATTERN = "**.***.***/****-##";
1096
+ function maskCNPJ(value, options = {}) {
1097
+ return applyDigitMask(
1098
+ ensureCNPJLength(value),
1099
+ options.pattern ?? DEFAULT_CNPJ_MASK_PATTERN
1100
+ );
1101
+ }
1102
+
1103
+ // src/services/cnpj/cnpj.generator.ts
1104
+ function generateCNPJ(options = {}) {
1105
+ const rootDigits = randomDigits(8);
1106
+ const branchDigits = normalizeCNPJBranch(options.branch).split("").map(Number);
1107
+ const baseDigits = [...rootDigits, ...branchDigits];
1108
+ const firstCheckDigit = calculateCNPJCheckDigit(baseDigits);
1109
+ const secondCheckDigit = calculateCNPJCheckDigit([
1110
+ ...baseDigits,
1111
+ firstCheckDigit
1112
+ ]);
1113
+ const cnpj = [...baseDigits, firstCheckDigit, secondCheckDigit].join("");
1114
+ return options.formatted ? formatCNPJ(cnpj) : cnpj;
1115
+ }
1116
+ function generateCNPJBatch(options) {
1117
+ const count = options.count;
1118
+ if (!Number.isInteger(count) || count <= 0)
1119
+ throw new BRUtilsError("Count must be a positive integer.");
1120
+ if (!options.unique)
1121
+ return Array.from(
1122
+ { length: count },
1123
+ () => generateCNPJ({ formatted: options.formatted, branch: options.branch })
1124
+ );
1125
+ const seen = /* @__PURE__ */ new Set();
1126
+ const values = [];
1127
+ while (values.length < count) {
1128
+ const value = generateCNPJ({
1129
+ formatted: options.formatted,
1130
+ branch: options.branch
1131
+ });
1132
+ if (!seen.has(value)) {
1133
+ seen.add(value);
1134
+ values.push(value);
1135
+ }
1136
+ }
1137
+ return values;
1138
+ }
1139
+
1140
+ // src/services/cnpj/cnpj.validator.ts
1141
+ function validateCNPJ(value, options = {}) {
1142
+ const digits = normalizeCNPJ(value);
1143
+ if (digits.length !== 14) return { isValid: false, value: digits };
1144
+ if (options.strict && allDigitsEqual(digits))
1145
+ return {
1146
+ isValid: false,
1147
+ value: digits,
1148
+ formatted: formatCNPJ(digits),
1149
+ branch: resolveCNPJBranch(digits)
1150
+ };
1151
+ const baseDigits = digits.slice(0, 12).split("").map(Number);
1152
+ const firstCheckDigit = calculateCNPJCheckDigit(baseDigits);
1153
+ const secondCheckDigit = calculateCNPJCheckDigit([
1154
+ ...baseDigits,
1155
+ firstCheckDigit
1156
+ ]);
1157
+ const expectedCNPJ = [...baseDigits, firstCheckDigit, secondCheckDigit].join(
1158
+ ""
1159
+ );
1160
+ return {
1161
+ isValid: digits === expectedCNPJ,
1162
+ value: digits,
1163
+ formatted: formatCNPJ(digits),
1164
+ branch: resolveCNPJBranch(digits)
1165
+ };
1166
+ }
1167
+
1168
+ // src/services/cpf/cpf.shared.ts
1169
+ var CPF_STATE_REGION_MAP = {
1170
+ AC: 2,
1171
+ AL: 4,
1172
+ AM: 2,
1173
+ AP: 2,
1174
+ BA: 5,
1175
+ CE: 3,
1176
+ DF: 1,
1177
+ ES: 7,
1178
+ GO: 1,
1179
+ MA: 3,
1180
+ MG: 6,
1181
+ MS: 1,
1182
+ MT: 1,
1183
+ PA: 2,
1184
+ PB: 4,
1185
+ PE: 4,
1186
+ PI: 3,
1187
+ PR: 9,
1188
+ RJ: 7,
1189
+ RN: 4,
1190
+ RO: 2,
1191
+ RR: 2,
1192
+ RS: 0,
1193
+ SC: 9,
1194
+ SE: 5,
1195
+ SP: 8,
1196
+ TO: 1
1197
+ };
1198
+ var REGION_TO_STATE = {
1199
+ 0: "RS",
1200
+ 1: "DF",
1201
+ 2: "AC",
1202
+ 3: "CE",
1203
+ 4: "AL",
1204
+ 5: "BA",
1205
+ 6: "MG",
1206
+ 7: "RJ",
1207
+ 8: "SP",
1208
+ 9: "PR"
1209
+ };
1210
+ function normalizeCPF(value) {
1211
+ return onlyDigits(value);
1212
+ }
1213
+ function stripCPF(value) {
1214
+ return normalizeCPF(value);
1215
+ }
1216
+ function ensureCPFLength(value) {
1217
+ const digits = normalizeCPF(value);
1218
+ if (digits.length !== 11)
1219
+ throw new BRUtilsError("CPF must contain exactly 11 digits.");
1220
+ return digits;
1221
+ }
1222
+ function calculateCPFCheckDigit(digits) {
1223
+ const factorStart = digits.length + 1;
1224
+ const sum = digits.reduce(
1225
+ (acc, digit, index) => acc + digit * (factorStart - index),
1226
+ 0
1227
+ );
1228
+ const remainder = sum * 10 % 11;
1229
+ return remainder === 10 ? 0 : remainder;
1230
+ }
1231
+ function resolveCPFRegionDigit(state) {
1232
+ return state ? CPF_STATE_REGION_MAP[state] : void 0;
1233
+ }
1234
+ function resolveCPFStateFromDigits(value) {
1235
+ const digits = normalizeCPF(value);
1236
+ if (digits.length < 9) return void 0;
1237
+ return REGION_TO_STATE[Number(digits[8])];
1238
+ }
1239
+
1240
+ // src/services/cpf/cpf.formatter.ts
1241
+ function formatCPFValue(value) {
1242
+ return formatCPF(ensureCPFLength(value));
1243
+ }
1244
+
1245
+ // src/services/cpf/cpf.masker.ts
1246
+ var DEFAULT_CPF_MASK_PATTERN = "***.***.***-##";
1247
+ function maskCPF(value, options = {}) {
1248
+ return applyDigitMask(
1249
+ ensureCPFLength(value),
1250
+ options.pattern ?? DEFAULT_CPF_MASK_PATTERN
1251
+ );
1252
+ }
1253
+
1254
+ // src/services/cpf/cpf.generator.ts
1255
+ function generateCPF(options = {}) {
1256
+ const firstEightDigits = randomDigits(8);
1257
+ const regionDigit = resolveCPFRegionDigit(options.state) ?? randomDigit();
1258
+ const baseDigits = [...firstEightDigits, regionDigit];
1259
+ const firstCheckDigit = calculateCPFCheckDigit(baseDigits);
1260
+ const secondCheckDigit = calculateCPFCheckDigit([
1261
+ ...baseDigits,
1262
+ firstCheckDigit
1263
+ ]);
1264
+ const cpf = [...baseDigits, firstCheckDigit, secondCheckDigit].join("");
1265
+ return options.formatted ? formatCPF(cpf) : cpf;
1266
+ }
1267
+ function generateCPFBatch(options) {
1268
+ const count = options.count;
1269
+ if (!Number.isInteger(count) || count <= 0)
1270
+ throw new BRUtilsError("Count must be a positive integer.");
1271
+ if (!options.unique)
1272
+ return Array.from(
1273
+ { length: count },
1274
+ () => generateCPF({ formatted: options.formatted, state: options.state })
1275
+ );
1276
+ const seen = /* @__PURE__ */ new Set();
1277
+ const values = [];
1278
+ while (values.length < count) {
1279
+ const value = generateCPF({
1280
+ formatted: options.formatted,
1281
+ state: options.state
1282
+ });
1283
+ if (!seen.has(value)) {
1284
+ seen.add(value);
1285
+ values.push(value);
1286
+ }
1287
+ }
1288
+ return values;
1289
+ }
1290
+
1291
+ // src/services/cpf/cpf.validator.ts
1292
+ function validateCPF(value, options = {}) {
1293
+ const digits = normalizeCPF(value);
1294
+ if (digits.length !== 11) return { isValid: false, value: digits };
1295
+ if (options.strict && allDigitsEqual(digits))
1296
+ return { isValid: false, value: digits, formatted: formatCPF(digits) };
1297
+ const baseDigits = digits.slice(0, 9).split("").map(Number);
1298
+ const firstCheckDigit = calculateCPFCheckDigit(baseDigits);
1299
+ const secondCheckDigit = calculateCPFCheckDigit([
1300
+ ...baseDigits,
1301
+ firstCheckDigit
1302
+ ]);
1303
+ const expectedCPF = [...baseDigits, firstCheckDigit, secondCheckDigit].join(
1304
+ ""
1305
+ );
1306
+ return {
1307
+ isValid: digits === expectedCPF,
1308
+ value: digits,
1309
+ formatted: formatCPF(digits),
1310
+ state: resolveCPFStateFromDigits(digits)
1311
+ };
1312
+ }
1313
+
1314
+ // src/services/credit-card/credit-card.constants.ts
1315
+ var CREDIT_CARD_BRAND_PREFIXES = {
1316
+ visa: ["4"],
1317
+ mastercard: ["51", "52", "53", "54", "55"],
1318
+ amex: ["34", "37"],
1319
+ elo: [
1320
+ "401178",
1321
+ "401179",
1322
+ "431274",
1323
+ "438935",
1324
+ "451416",
1325
+ "457393",
1326
+ "457631",
1327
+ "457632",
1328
+ "504175",
1329
+ "627780",
1330
+ "636297",
1331
+ "636368"
1332
+ ]
1333
+ };
1334
+ var CREDIT_CARD_BRAND_LENGTHS = {
1335
+ visa: [16],
1336
+ mastercard: [16],
1337
+ amex: [15],
1338
+ elo: [16]
1339
+ };
1340
+ var CREDIT_CARD_BRAND_CVV_LENGTH = {
1341
+ visa: 3,
1342
+ mastercard: 3,
1343
+ amex: 4,
1344
+ elo: 3
1345
+ };
1346
+
1347
+ // src/services/credit-card/credit-card.detector.ts
1348
+ function detectCreditCardBrand(number) {
1349
+ const digits = onlyDigits(number);
1350
+ const brands = Object.keys(CREDIT_CARD_BRAND_PREFIXES);
1351
+ for (const brand of brands) {
1352
+ const prefixes = CREDIT_CARD_BRAND_PREFIXES[brand];
1353
+ const lengths = CREDIT_CARD_BRAND_LENGTHS[brand];
1354
+ const matchesPrefix = prefixes.some((prefix) => digits.startsWith(prefix));
1355
+ const matchesLength = lengths.includes(digits.length);
1356
+ if (matchesPrefix && matchesLength) {
1357
+ return brand;
1358
+ }
1359
+ }
1360
+ return "unknown";
1361
+ }
1362
+
1363
+ // src/core/utils/array.ts
1364
+ function pickRandomItem(items, randomSource = Math.random) {
1365
+ const item = items[Math.floor(randomSource() * items.length)];
1366
+ if (item === void 0) {
1367
+ throw new BRUtilsError("Cannot pick a random item from an empty array.");
1368
+ }
1369
+ return item;
1370
+ }
1371
+ function getItemAtIndex(items, index) {
1372
+ const item = items[index];
1373
+ if (item === void 0) {
1374
+ throw new BRUtilsError(`Array index out of bounds: ${index}`);
1375
+ }
1376
+ return item;
1377
+ }
1378
+ function shuffleArray(items, randomSource = Math.random) {
1379
+ const result = [...items];
1380
+ for (let index = result.length - 1; index > 0; index -= 1) {
1381
+ const swapIndex = Math.floor(randomSource() * (index + 1));
1382
+ const currentValue = getItemAtIndex(result, index);
1383
+ const swapValue = getItemAtIndex(result, swapIndex);
1384
+ result[index] = swapValue;
1385
+ result[swapIndex] = currentValue;
1386
+ }
1387
+ return result;
1388
+ }
1389
+
1390
+ // src/services/credit-card/credit-card.generator.ts
1391
+ function generateRandomNumericString(length) {
1392
+ return Array.from({ length }, () => String(randomDigit())).join("");
1393
+ }
1394
+ function formatCreditCardNumber(value) {
1395
+ return onlyDigits(value).replace(/(\d{4})(?=\d)/g, "$1 ").trim();
1396
+ }
1397
+ function calculateLuhnCheckDigit(partialNumber) {
1398
+ const digits = partialNumber.split("").map(Number).reverse();
1399
+ const sum = digits.reduce((accumulator, digit, index) => {
1400
+ if (index % 2 === 0) {
1401
+ const doubled = digit * 2;
1402
+ return accumulator + (doubled > 9 ? doubled - 9 : doubled);
1403
+ }
1404
+ return accumulator + digit;
1405
+ }, 0);
1406
+ return (10 - sum % 10) % 10;
1407
+ }
1408
+ function generateCardNumber(brand) {
1409
+ const prefixes = CREDIT_CARD_BRAND_PREFIXES[brand];
1410
+ const lengths = CREDIT_CARD_BRAND_LENGTHS[brand];
1411
+ const prefix = pickRandomItem(prefixes);
1412
+ const totalLength = pickRandomItem(lengths);
1413
+ const bodyLength = totalLength - prefix.length - 1;
1414
+ const partialNumber = prefix + generateRandomNumericString(bodyLength);
1415
+ const checkDigit = calculateLuhnCheckDigit(partialNumber);
1416
+ return partialNumber + String(checkDigit);
1417
+ }
1418
+ function generateExpiry(expiryYearsAhead = 5) {
1419
+ const now = /* @__PURE__ */ new Date();
1420
+ const month = String(Math.floor(Math.random() * 12) + 1).padStart(2, "0");
1421
+ const futureYear = now.getFullYear() + Math.floor(Math.random() * Math.max(expiryYearsAhead, 1)) + 1;
1422
+ const yearTwoDigits = String(futureYear).slice(-2);
1423
+ return {
1424
+ expiryMonth: month,
1425
+ expiryYear: yearTwoDigits,
1426
+ expiry: `${month}/${yearTwoDigits}`
1427
+ };
1428
+ }
1429
+ function generateCVV(brand) {
1430
+ const length = CREDIT_CARD_BRAND_CVV_LENGTH[brand];
1431
+ return generateRandomNumericString(length);
1432
+ }
1433
+ function generateCreditCard(options = {}) {
1434
+ const brand = options.brand ?? "visa";
1435
+ const rawNumber = generateCardNumber(brand);
1436
+ const number = options.formatted ? formatCreditCardNumber(rawNumber) : rawNumber;
1437
+ const { expiryMonth, expiryYear, expiry } = generateExpiry(
1438
+ options.expiryYearsAhead
1439
+ );
1440
+ const cvv = generateCVV(brand);
1441
+ return {
1442
+ brand,
1443
+ number,
1444
+ expiryMonth,
1445
+ expiryYear,
1446
+ expiry,
1447
+ cvv
1448
+ };
1449
+ }
1450
+
1451
+ // src/services/credit-card/credit-card.validator.ts
1452
+ function isValidLuhn(number) {
1453
+ const digits = onlyDigits(number);
1454
+ let sum = 0;
1455
+ let shouldDouble = false;
1456
+ for (let index = digits.length - 1; index >= 0; index -= 1) {
1457
+ let digit = Number(digits[index]);
1458
+ if (shouldDouble) {
1459
+ digit *= 2;
1460
+ if (digit > 9) {
1461
+ digit -= 9;
1462
+ }
1463
+ }
1464
+ sum += digit;
1465
+ shouldDouble = !shouldDouble;
1466
+ }
1467
+ return sum % 10 === 0;
1468
+ }
1469
+ function isValidExpiry(expiryMonth, expiryYear, expiry) {
1470
+ let month = expiryMonth;
1471
+ let year = expiryYear;
1472
+ if ((!month || !year) && expiry) {
1473
+ const match = expiry.match(/^(\d{2})\/(\d{2})$/);
1474
+ if (!match) {
1475
+ return false;
1476
+ }
1477
+ month = match[1];
1478
+ year = match[2];
1479
+ }
1480
+ if (!month || !year) {
1481
+ return false;
1482
+ }
1483
+ const monthNumber = Number(month);
1484
+ const yearNumber = Number(year);
1485
+ if (monthNumber < 1 || monthNumber > 12) {
1486
+ return false;
1487
+ }
1488
+ const now = /* @__PURE__ */ new Date();
1489
+ const currentYear = Number(String(now.getFullYear()).slice(-2));
1490
+ const currentMonth = now.getMonth() + 1;
1491
+ if (yearNumber > currentYear) {
1492
+ return true;
1493
+ }
1494
+ if (yearNumber === currentYear && monthNumber >= currentMonth) {
1495
+ return true;
1496
+ }
1497
+ return false;
1498
+ }
1499
+ function isValidCVV(brand, cvv) {
1500
+ if (!cvv) {
1501
+ return false;
1502
+ }
1503
+ const digits = onlyDigits(cvv);
1504
+ if (brand === "unknown") {
1505
+ return digits.length >= 3 && digits.length <= 4;
1506
+ }
1507
+ const expectedLength = CREDIT_CARD_BRAND_CVV_LENGTH[brand];
1508
+ return digits.length === expectedLength;
1509
+ }
1510
+ function validateCreditCard(input) {
1511
+ const number = onlyDigits(input.number);
1512
+ const brand = detectCreditCardBrand(number);
1513
+ const numberValid = brand !== "unknown" && CREDIT_CARD_BRAND_LENGTHS[brand].includes(number.length) && isValidLuhn(number);
1514
+ const expiryValid = isValidExpiry(
1515
+ input.expiryMonth,
1516
+ input.expiryYear,
1517
+ input.expiry
1518
+ );
1519
+ const cvvValid = isValidCVV(brand, input.cvv);
1520
+ return {
1521
+ isValid: numberValid && expiryValid && cvvValid,
1522
+ brand,
1523
+ number,
1524
+ numberValid,
1525
+ expiryValid,
1526
+ cvvValid
1527
+ };
1528
+ }
1529
+
1530
+ // src/cli/commands/register-brazilian.ts
1531
+ function registerBrazilianCommands(program) {
1532
+ const cpf = program.command("cpf").description("Generate, validate, format, strip and mask CPF values.").addHelpText(
1533
+ "after",
1534
+ examples([
1535
+ "brutils cpf generate --formatted",
1536
+ "brutils cpf generate --formatted --state SP --count 5 --unique",
1537
+ "brutils cpf validate 52998224725 --strict",
1538
+ "brutils cpf format 52998224725",
1539
+ "brutils cpf strip 529.982.247-25",
1540
+ 'brutils cpf mask 52998224725 --mask "***.###.***-##"'
1541
+ ])
1542
+ );
1543
+ cpf.command("generate").description("Generate synthetic valid CPF values.").option("--formatted", "Return generated values with CPF punctuation.").option(
1544
+ "--state <uf>",
1545
+ "Bias the region digit using a Brazilian state code.",
1546
+ parseState
1547
+ ).option(
1548
+ "--count <number>",
1549
+ "Generate multiple CPFs.",
1550
+ parsePositiveInteger,
1551
+ 1
1552
+ ).option("--unique", "Avoid duplicates during batch generation.").action(
1553
+ (options) => {
1554
+ const values = options.count > 1 ? generateCPFBatch({
1555
+ formatted: options.formatted,
1556
+ state: options.state,
1557
+ count: options.count,
1558
+ unique: options.unique
1559
+ }) : [
1560
+ generateCPF({
1561
+ formatted: options.formatted,
1562
+ state: options.state
1563
+ })
1564
+ ];
1565
+ printValue(values);
1566
+ }
1567
+ );
1568
+ cpf.command("validate").argument("<value>", "CPF value to validate.").description(
1569
+ "Validate a CPF using size, check digits and optional strict repeated-pattern rejection."
1570
+ ).option("--strict", "Reject repeated patterns such as 11111111111.").action((value, options) => {
1571
+ printValue(validateCPF(value, { strict: options.strict }));
1572
+ });
1573
+ cpf.command("format").argument("<value>", "CPF value to format.").description("Format a CPF as 000.000.000-00.").action((value) => {
1574
+ printValue(formatCPFValue(value));
1575
+ });
1576
+ cpf.command("strip").argument("<value>", "CPF value to normalize to digits only.").description("Remove punctuation and keep digits only.").action((value) => {
1577
+ printValue(stripCPF(value));
1578
+ });
1579
+ cpf.command("mask").argument("<value>", "CPF value to mask.").description("Mask a CPF for safe display.").option(
1580
+ "--mask <pattern>",
1581
+ 'Custom mask. Use "*" to hide digits and "#" to reveal them.'
1582
+ ).action((value, options) => {
1583
+ printValue(maskCPF(value, { pattern: options.mask }));
1584
+ });
1585
+ const cnpj = program.command("cnpj").description("Generate, validate, format, strip and mask CNPJ values.").addHelpText(
1586
+ "after",
1587
+ examples([
1588
+ "brutils cnpj generate --formatted",
1589
+ "brutils cnpj generate --branch 12 --count 3 --unique",
1590
+ "brutils cnpj validate 11.444.777/0001-61 --strict",
1591
+ "brutils cnpj format 11444777000161",
1592
+ "brutils cnpj strip 11.444.777/0001-61",
1593
+ 'brutils cnpj mask 11444777000161 --mask "##.***.***/****-##"'
1594
+ ])
1595
+ );
1596
+ cnpj.command("generate").description("Generate synthetic valid CNPJ values.").option("--formatted", "Return generated values with CNPJ punctuation.").option(
1597
+ "--branch <number>",
1598
+ "Force a branch identifier. Values are left-padded to 4 digits."
1599
+ ).option(
1600
+ "--count <number>",
1601
+ "Generate multiple CNPJs.",
1602
+ parsePositiveInteger,
1603
+ 1
1604
+ ).option("--unique", "Avoid duplicates during batch generation.").action(
1605
+ (options) => {
1606
+ const values = options.count > 1 ? generateCNPJBatch({
1607
+ formatted: options.formatted,
1608
+ branch: options.branch,
1609
+ count: options.count,
1610
+ unique: options.unique
1611
+ }) : [
1612
+ generateCNPJ({
1613
+ formatted: options.formatted,
1614
+ branch: options.branch
1615
+ })
1616
+ ];
1617
+ printValue(values);
1618
+ }
1619
+ );
1620
+ cnpj.command("validate").argument("<value>", "CNPJ value to validate.").description(
1621
+ "Validate a CNPJ using size, check digits and optional strict repeated-pattern rejection."
1622
+ ).option("--strict", "Reject repeated patterns such as 00000000000000.").action((value, options) => {
1623
+ printValue(validateCNPJ(value, { strict: options.strict }));
1624
+ });
1625
+ cnpj.command("format").argument("<value>", "CNPJ value to format.").description("Format a CNPJ as 00.000.000/0000-00.").action((value) => {
1626
+ printValue(formatCNPJValue(value));
1627
+ });
1628
+ cnpj.command("strip").argument("<value>", "CNPJ value to normalize to digits only.").description("Remove punctuation and keep digits only.").action((value) => {
1629
+ printValue(stripCNPJ(value));
1630
+ });
1631
+ cnpj.command("mask").argument("<value>", "CNPJ value to mask.").description("Mask a CNPJ for safe display.").option(
1632
+ "--mask <pattern>",
1633
+ 'Custom mask. Use "*" to hide digits and "#" to reveal them.'
1634
+ ).action((value, options) => {
1635
+ printValue(maskCNPJ(value, { pattern: options.mask }));
1636
+ });
1637
+ const cep = program.command("cep").description("Generate, validate, format, strip and mask CEP values.").addHelpText(
1638
+ "after",
1639
+ examples([
1640
+ "brutils cep generate --formatted",
1641
+ "brutils cep generate --state PR --count 5",
1642
+ "brutils cep validate 86010-190 --strict",
1643
+ "brutils cep format 86010190",
1644
+ "brutils cep strip 86010-190",
1645
+ 'brutils cep mask 86010190 --mask "###**-***"'
1646
+ ])
1647
+ );
1648
+ cep.command("generate").description("Generate synthetic CEP values for testing.").option("--formatted", "Return generated values with CEP punctuation.").option(
1649
+ "--state <uf>",
1650
+ "Bias the leading digit using a Brazilian state code.",
1651
+ parseState
1652
+ ).option(
1653
+ "--count <number>",
1654
+ "Generate multiple CEP values.",
1655
+ parsePositiveInteger,
1656
+ 1
1657
+ ).action(
1658
+ (options) => {
1659
+ const values = options.count > 1 ? generateCEPBatch({
1660
+ formatted: options.formatted,
1661
+ state: options.state,
1662
+ count: options.count
1663
+ }) : [
1664
+ generateCEP({
1665
+ formatted: options.formatted,
1666
+ state: options.state
1667
+ })
1668
+ ];
1669
+ printValue(values);
1670
+ }
1671
+ );
1672
+ cep.command("validate").argument("<value>", "CEP value to validate.").description(
1673
+ "Validate CEP structure and optional strict repeated-pattern rejection."
1674
+ ).option("--strict", "Reject repeated patterns such as 11111111.").action((value, options) => {
1675
+ printValue(validateCEP(value, { strict: options.strict }));
1676
+ });
1677
+ cep.command("format").argument("<value>", "CEP value to format.").description("Format a CEP as 00000-000.").action((value) => {
1678
+ printValue(formatCEPValue(value));
1679
+ });
1680
+ cep.command("strip").argument("<value>", "CEP value to normalize to digits only.").description("Remove punctuation and keep digits only.").action((value) => {
1681
+ printValue(stripCEP(value));
1682
+ });
1683
+ cep.command("mask").argument("<value>", "CEP value to mask.").description("Mask a CEP for safe display.").option(
1684
+ "--mask <pattern>",
1685
+ 'Custom mask. Use "*" to hide digits and "#" to reveal them.'
1686
+ ).action((value, options) => {
1687
+ printValue(maskCEP(value, { pattern: options.mask }));
1688
+ });
1689
+ const creditCard = program.command("credit-card").alias("card").description("Generate, validate and detect test credit card data.").addHelpText(
1690
+ "after",
1691
+ examples([
1692
+ "brutils credit-card generate --brand visa --formatted",
1693
+ "brutils credit-card validate --number 4111111111111111 --expiry 12/30 --cvv 123",
1694
+ "brutils credit-card validate --number 4111111111111111 --expiry-month 12 --expiry-year 30 --cvv 123",
1695
+ "brutils credit-card detect 4111111111111111"
1696
+ ])
1697
+ );
1698
+ creditCard.command("generate").description("Generate synthetic card test data.").option("--brand <brand>", "Card brand to generate.", parseCardBrand).option("--formatted", "Return the card number with spaces every 4 digits.").option(
1699
+ "--expiry-years-ahead <number>",
1700
+ "Maximum number of years ahead for generated expiry.",
1701
+ parsePositiveInteger
1702
+ ).action(
1703
+ (options) => {
1704
+ printValue(generateCreditCard(options));
1705
+ }
1706
+ );
1707
+ creditCard.command("validate").description("Validate card number, expiry and CVV.").requiredOption("--number <value>", "Card number to validate.").option("--expiry <value>", "Expiry in MM/YY format.").option("--expiry-month <value>", "Expiry month.").option("--expiry-year <value>", "Expiry year.").requiredOption("--cvv <value>", "CVV to validate.").action(
1708
+ (options) => {
1709
+ printValue(validateCreditCard(options));
1710
+ }
1711
+ );
1712
+ creditCard.command("detect").argument("<number>", "Card number to inspect.").description("Detect the card brand from a card number.").action((number) => {
1713
+ printValue(detectCreditCardBrand(number));
1714
+ });
1715
+ }
1716
+
1717
+ // src/services/date/date.service.ts
1718
+ var DATE_DIFF_FACTORS = {
1719
+ seconds: 1e3,
1720
+ minutes: 60 * 1e3,
1721
+ hours: 60 * 60 * 1e3,
1722
+ days: 24 * 60 * 60 * 1e3
1723
+ };
1724
+ function ensureValidDate(date, input) {
1725
+ if (Number.isNaN(date.getTime())) {
1726
+ throw new BRUtilsError(`Invalid date value: ${input}`);
1727
+ }
1728
+ }
1729
+ function parseDate(input) {
1730
+ const date = new Date(input);
1731
+ ensureValidDate(date, input);
1732
+ return date;
1733
+ }
1734
+ function ensureValidInteger(value, label) {
1735
+ const resolved = value ?? 0;
1736
+ if (!Number.isInteger(resolved)) {
1737
+ throw new BRUtilsError(`${label} must be an integer.`);
1738
+ }
1739
+ return resolved;
1740
+ }
1741
+ function pad2(value) {
1742
+ return String(value).padStart(2, "0");
1743
+ }
1744
+ function snapshot(date) {
1745
+ const unixMs = date.getTime();
1746
+ return {
1747
+ iso: date.toISOString(),
1748
+ unix: Math.floor(unixMs / 1e3),
1749
+ unixMs
1750
+ };
1751
+ }
1752
+ function formatUtcDate(date, pattern) {
1753
+ const replacements = {
1754
+ YYYY: String(date.getUTCFullYear()),
1755
+ MM: pad2(date.getUTCMonth() + 1),
1756
+ DD: pad2(date.getUTCDate()),
1757
+ HH: pad2(date.getUTCHours()),
1758
+ mm: pad2(date.getUTCMinutes()),
1759
+ ss: pad2(date.getUTCSeconds())
1760
+ };
1761
+ return pattern.replace(
1762
+ /YYYY|MM|DD|HH|mm|ss/g,
1763
+ (token) => replacements[token]
1764
+ );
1765
+ }
1766
+ function adjustDate(date, options, multiplier) {
1767
+ const days = ensureValidInteger(options.days, "Days");
1768
+ const hours = ensureValidInteger(options.hours, "Hours");
1769
+ const minutes = ensureValidInteger(options.minutes, "Minutes");
1770
+ const seconds = ensureValidInteger(options.seconds, "Seconds");
1771
+ const totalMs = days * DATE_DIFF_FACTORS.days + hours * DATE_DIFF_FACTORS.hours + minutes * DATE_DIFF_FACTORS.minutes + seconds * DATE_DIFF_FACTORS.seconds;
1772
+ return new Date(date.getTime() + multiplier * totalMs);
1773
+ }
1774
+ function ensureTimeZone(value) {
1775
+ try {
1776
+ new Intl.DateTimeFormat("en-US", { timeZone: value }).format(/* @__PURE__ */ new Date());
1777
+ return value;
1778
+ } catch {
1779
+ throw new BRUtilsError(`Invalid time zone: ${value}`);
1780
+ }
1781
+ }
1782
+ function timeZoneParts(date, timeZone) {
1783
+ const formatter = new Intl.DateTimeFormat("en-CA", {
1784
+ timeZone,
1785
+ year: "numeric",
1786
+ month: "2-digit",
1787
+ day: "2-digit",
1788
+ hour: "2-digit",
1789
+ minute: "2-digit",
1790
+ second: "2-digit",
1791
+ hour12: false
1792
+ });
1793
+ return formatter.formatToParts(date).reduce((acc, part) => {
1794
+ if (part.type !== "literal") {
1795
+ acc[part.type] = part.value;
1796
+ }
1797
+ return acc;
1798
+ }, {});
1799
+ }
1800
+ function currentDateTime() {
1801
+ return snapshot(/* @__PURE__ */ new Date());
1802
+ }
1803
+ function formatDateValue(value, pattern) {
1804
+ return formatUtcDate(parseDate(value), pattern);
1805
+ }
1806
+ function parseDateValue(value) {
1807
+ return {
1808
+ input: value,
1809
+ ...snapshot(parseDate(value))
1810
+ };
1811
+ }
1812
+ function addToDate(value, options) {
1813
+ return adjustDate(parseDate(value), options, 1).toISOString();
1814
+ }
1815
+ function subtractFromDate(value, options) {
1816
+ return adjustDate(parseDate(value), options, -1).toISOString();
1817
+ }
1818
+ function diffDates(from, to, unit = "seconds") {
1819
+ const left = parseDate(from);
1820
+ const right = parseDate(to);
1821
+ const factor = DATE_DIFF_FACTORS[unit];
1822
+ const raw = (right.getTime() - left.getTime()) / factor;
1823
+ return {
1824
+ from,
1825
+ to,
1826
+ unit,
1827
+ value: Number(raw.toFixed(6))
1828
+ };
1829
+ }
1830
+ function convertDateToUnix(value) {
1831
+ return {
1832
+ input: value,
1833
+ sourceUnit: "milliseconds",
1834
+ ...snapshot(parseDate(value))
1835
+ };
1836
+ }
1837
+ function convertUnixValue(value) {
1838
+ const normalized = String(value).trim();
1839
+ if (!/^-?\d+$/.test(normalized)) {
1840
+ throw new BRUtilsError(`Invalid Unix timestamp value: ${value}`);
1841
+ }
1842
+ const numeric = Number(normalized);
1843
+ const sourceUnit = Math.abs(numeric) >= 1e12 ? "milliseconds" : "seconds";
1844
+ const unixMs = sourceUnit === "seconds" ? numeric * 1e3 : numeric;
1845
+ const date = new Date(unixMs);
1846
+ ensureValidDate(date, normalized);
1847
+ return {
1848
+ input: value,
1849
+ sourceUnit,
1850
+ ...snapshot(date)
1851
+ };
1852
+ }
1853
+ function convertDateToTimeZone(value, timeZone) {
1854
+ const date = parseDate(value);
1855
+ const normalizedTimeZone = ensureTimeZone(timeZone);
1856
+ const parts = timeZoneParts(date, normalizedTimeZone);
1857
+ return {
1858
+ input: value,
1859
+ timeZone: normalizedTimeZone,
1860
+ formatted: `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}:${parts.second}`,
1861
+ iso: date.toISOString()
1862
+ };
1863
+ }
1864
+
1865
+ // src/services/hash/hash.service.ts
1866
+ import crypto from "crypto";
1867
+ import fs5 from "fs";
1868
+ function resolveHashSource(options) {
1869
+ if (options.text !== void 0 && options.file !== void 0) {
1870
+ throw new BRUtilsError("Use either --text or --file, not both.");
1871
+ }
1872
+ if (options.text !== void 0) {
1873
+ return {
1874
+ kind: "text",
1875
+ content: Buffer.from(options.text, "utf-8")
1876
+ };
1877
+ }
1878
+ if (options.file !== void 0) {
1879
+ if (!fs5.existsSync(options.file) || !fs5.statSync(options.file).isFile()) {
1880
+ throw new BRUtilsError(`File does not exist: ${options.file}`);
1881
+ }
1882
+ return {
1883
+ kind: "file",
1884
+ content: fs5.readFileSync(options.file)
1885
+ };
1886
+ }
1887
+ throw new BRUtilsError("One of --text or --file is required.");
1888
+ }
1889
+ function normalizeAlgorithm(value) {
1890
+ const algorithm = value.toLowerCase();
1891
+ if (!crypto.getHashes().includes(algorithm)) {
1892
+ throw new BRUtilsError(`Unsupported hash algorithm: ${value}`);
1893
+ }
1894
+ return algorithm;
1895
+ }
1896
+ function digestBuffer(buffer, algorithm) {
1897
+ return crypto.createHash(normalizeAlgorithm(algorithm)).update(buffer).digest("hex");
1898
+ }
1899
+ function normalizeExpectedHash(value) {
1900
+ return value.trim().toLowerCase();
1901
+ }
1902
+ function computeHash(options, algorithm) {
1903
+ const source = resolveHashSource(options);
1904
+ return digestBuffer(source.content, algorithm);
1905
+ }
1906
+ function computeMd5(options) {
1907
+ return computeHash(options, "md5");
1908
+ }
1909
+ function computeSha1(options) {
1910
+ return computeHash(options, "sha1");
1911
+ }
1912
+ function computeSha256(options) {
1913
+ return computeHash(options, "sha256");
1914
+ }
1915
+ function computeSha512(options) {
1916
+ return computeHash(options, "sha512");
1917
+ }
1918
+ function computeHmac(options) {
1919
+ const source = resolveHashSource(options);
1920
+ return crypto.createHmac(normalizeAlgorithm(options.algo), options.key).update(source.content).digest("hex");
1921
+ }
1922
+ function computeChecksum(options) {
1923
+ return computeHash({ file: options.file }, options.algo ?? "sha256");
1924
+ }
1925
+ function compareHash(options) {
1926
+ const source = resolveHashSource(options);
1927
+ const algorithm = normalizeAlgorithm(options.algo ?? "sha256");
1928
+ const actual = digestBuffer(source.content, algorithm);
1929
+ const expected = normalizeExpectedHash(options.expected);
1930
+ return {
1931
+ algorithm,
1932
+ actual,
1933
+ expected,
1934
+ matches: actual === expected,
1935
+ source: source.kind
1936
+ };
1937
+ }
1938
+
1939
+ // src/services/id/id.service.ts
1940
+ import crypto2 from "crypto";
1941
+ var LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
1942
+ var UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1943
+ var DIGITS = "0123456789";
1944
+ var SYMBOLS = "!@#$%^&*()-_=+[]{};:,.?/|~";
1945
+ var CHARSET_MAP = {
1946
+ alnum: `${LOWERCASE}${UPPERCASE}${DIGITS}`,
1947
+ alpha: `${LOWERCASE}${UPPERCASE}`,
1948
+ numeric: DIGITS,
1949
+ hex: "0123456789abcdef",
1950
+ base64url: `${LOWERCASE}${UPPERCASE}${DIGITS}-_`,
1951
+ lower: LOWERCASE,
1952
+ upper: UPPERCASE
1953
+ };
1954
+ function ensurePositiveInteger(value, label) {
1955
+ if (!Number.isInteger(value) || value < 1) {
1956
+ throw new BRUtilsError(`${label} must be a positive integer.`);
1957
+ }
1958
+ }
1959
+ function resolveCount(count) {
1960
+ const resolved = count ?? 1;
1961
+ ensurePositiveInteger(resolved, "Count");
1962
+ return resolved;
1963
+ }
1964
+ function resolveLength(length, fallback) {
1965
+ const resolved = length ?? fallback;
1966
+ ensurePositiveInteger(resolved, "Length");
1967
+ return resolved;
1968
+ }
1969
+ function charsetByName(name) {
1970
+ return CHARSET_MAP[name];
1971
+ }
1972
+ function randomCharacter(charset) {
1973
+ return charset[crypto2.randomInt(0, charset.length)];
1974
+ }
1975
+ function randomString(length, charset) {
1976
+ return Array.from({ length }, () => randomCharacter(charset)).join("");
1977
+ }
1978
+ function ensureNotEmptyCharset(charset) {
1979
+ if (charset.length === 0) {
1980
+ throw new BRUtilsError(
1981
+ "The selected options produced an empty character set."
1982
+ );
1983
+ }
1984
+ }
1985
+ function ensureUppercasePresence(value) {
1986
+ if (/[A-Z]/.test(value)) {
1987
+ return value;
1988
+ }
1989
+ const index = crypto2.randomInt(0, value.length);
1990
+ const replacement = randomCharacter(UPPERCASE);
1991
+ return `${value.slice(0, index)}${replacement}${value.slice(index + 1)}`;
1992
+ }
1993
+ function resolveTokenCharset(charset) {
1994
+ return charsetByName(charset ?? "alnum");
1995
+ }
1996
+ function resolvePasswordCharset(options) {
1997
+ let charset = options.charset ? charsetByName(options.charset) : `${LOWERCASE}${UPPERCASE}${DIGITS}${SYMBOLS}`;
1998
+ if (options.noNumbers) {
1999
+ charset = charset.replace(/[0-9]/g, "");
2000
+ }
2001
+ if (options.noSymbols) {
2002
+ charset = charset.replace(/[!@#$%^&*()_=+[\]{};:,.?/|~-]/g, "");
2003
+ }
2004
+ if (options.uppercase && !/[A-Z]/.test(charset)) {
2005
+ charset += UPPERCASE;
2006
+ }
2007
+ ensureNotEmptyCharset(charset);
2008
+ return charset;
2009
+ }
2010
+ function generateUuidValues(options = {}) {
2011
+ const count = resolveCount(options.count);
2012
+ return Array.from({ length: count }, () => crypto2.randomUUID());
2013
+ }
2014
+ function generateTokenValues(options = {}) {
2015
+ const count = resolveCount(options.count);
2016
+ const length = resolveLength(options.length, 32);
2017
+ const charset = resolveTokenCharset(options.charset);
2018
+ return Array.from({ length: count }, () => randomString(length, charset));
2019
+ }
2020
+ function generatePasswordValues(options = {}) {
2021
+ const count = resolveCount(options.count);
2022
+ const length = resolveLength(options.length, 16);
2023
+ const charset = resolvePasswordCharset(options);
2024
+ return Array.from({ length: count }, () => {
2025
+ let password = randomString(length, charset);
2026
+ if (options.uppercase) {
2027
+ password = ensureUppercasePresence(password);
2028
+ }
2029
+ return password;
2030
+ });
2031
+ }
2032
+
2033
+ // src/cli/commands/register-helpers.ts
2034
+ function printStringBatch(values) {
2035
+ if (values.length === 1) {
2036
+ printValue(values[0]);
2037
+ return;
2038
+ }
2039
+ printValue(values);
2040
+ }
2041
+ function registerHelperCommands(program) {
2042
+ const hash = program.command("hash").description("Digest helpers for text, files and HMAC operations.").addHelpText(
2043
+ "after",
2044
+ examples([
2045
+ "brutils hash md5 --text hello",
2046
+ "brutils hash sha256 --file ./README.md",
2047
+ "brutils hash hmac --algo sha256 --key secret --text hello",
2048
+ "brutils hash checksum --file ./dist/cli.js --algo sha512",
2049
+ "brutils hash compare --file ./README.md --algo sha256 --expected <hash>"
2050
+ ])
2051
+ );
2052
+ hash.command("md5").description("Compute an MD5 digest for text or a file.").option("--text <value>", "Inline value to hash.").option("--file <path>", "File to hash.").action((options) => {
2053
+ printValue(computeMd5(options));
2054
+ });
2055
+ hash.command("sha1").description("Compute a SHA-1 digest for text or a file.").option("--text <value>", "Inline value to hash.").option("--file <path>", "File to hash.").action((options) => {
2056
+ printValue(computeSha1(options));
2057
+ });
2058
+ hash.command("sha256").description("Compute a SHA-256 digest for text or a file.").option("--text <value>", "Inline value to hash.").option("--file <path>", "File to hash.").action((options) => {
2059
+ printValue(computeSha256(options));
2060
+ });
2061
+ hash.command("sha512").description("Compute a SHA-512 digest for text or a file.").option("--text <value>", "Inline value to hash.").option("--file <path>", "File to hash.").action((options) => {
2062
+ printValue(computeSha512(options));
2063
+ });
2064
+ hash.command("hmac").description("Compute an HMAC with a chosen algorithm.").requiredOption("--key <value>", "Secret key for the HMAC.").option("--algo <name>", "Hash algorithm to use.", "sha256").option("--text <value>", "Inline value to hash.").option("--file <path>", "File to hash.").action(
2065
+ (options) => {
2066
+ printValue(computeHmac(options));
2067
+ }
2068
+ );
2069
+ hash.command("checksum").description("Compute a digest for a file.").requiredOption("--file <path>", "File to hash.").option("--algo <name>", "Hash algorithm to use.", "sha256").action((options) => {
2070
+ printValue(computeChecksum(options));
2071
+ });
2072
+ hash.command("compare").description("Compare a computed hash against an expected value.").requiredOption("--expected <hash>", "Expected hash value.").option("--algo <name>", "Hash algorithm to use.", "sha256").option("--text <value>", "Inline value to hash.").option("--file <path>", "File to hash.").action(
2073
+ (options) => {
2074
+ printValue(compareHash(options));
2075
+ }
2076
+ );
2077
+ const id = program.command("id").description("UUID, token and password generation helpers.").addHelpText(
2078
+ "after",
2079
+ examples([
2080
+ "brutils id uuid",
2081
+ "brutils id uuid --count 3",
2082
+ "brutils id token --length 24 --charset base64url",
2083
+ "brutils id password --length 20 --uppercase",
2084
+ "brutils id password --length 16 --no-symbols --no-numbers"
2085
+ ])
2086
+ );
2087
+ id.command("uuid").description("Generate UUID values.").option(
2088
+ "--count <number>",
2089
+ "How many UUIDs to generate.",
2090
+ parsePositiveInteger,
2091
+ 1
2092
+ ).action((options) => {
2093
+ printStringBatch(generateUuidValues(options));
2094
+ });
2095
+ id.command("token").description("Generate random tokens.").option(
2096
+ "--count <number>",
2097
+ "How many tokens to generate.",
2098
+ parsePositiveInteger,
2099
+ 1
2100
+ ).option("--length <number>", "Desired token length.", parsePositiveInteger).option(
2101
+ "--charset <name>",
2102
+ "Allowed character preset: alnum, alpha, numeric, hex, base64url, lower or upper.",
2103
+ parseCharsetName
2104
+ ).action(
2105
+ (options) => {
2106
+ printStringBatch(generateTokenValues(options));
2107
+ }
2108
+ );
2109
+ id.command("password").description("Generate strong passwords.").option(
2110
+ "--count <number>",
2111
+ "How many passwords to generate.",
2112
+ parsePositiveInteger,
2113
+ 1
2114
+ ).option(
2115
+ "--length <number>",
2116
+ "Desired password length.",
2117
+ parsePositiveInteger
2118
+ ).option(
2119
+ "--charset <name>",
2120
+ "Allowed character preset: alnum, alpha, numeric, hex, base64url, lower or upper.",
2121
+ parseCharsetName
2122
+ ).option("--no-symbols", "Exclude symbols from generated passwords.").option("--no-numbers", "Exclude digits from generated passwords.").option("--uppercase", "Ensure at least one uppercase letter.").action(
2123
+ (options) => {
2124
+ printStringBatch(generatePasswordValues(options));
2125
+ }
2126
+ );
2127
+ const date = program.command("date").description("Date formatting, arithmetic and timezone helpers.").addHelpText(
2128
+ "after",
2129
+ examples([
2130
+ "brutils date now",
2131
+ 'brutils date format --date 2024-01-02T03:04:05Z --pattern "YYYY-MM-DD HH:mm:ss"',
2132
+ "brutils date add --date 2024-01-01T00:00:00Z --days 7",
2133
+ "brutils date diff --from 2024-01-01T00:00:00Z --to 2024-01-03T00:00:00Z --unit days",
2134
+ "brutils date unix --date 2024-01-01T00:00:00Z",
2135
+ "brutils date tz --from 2024-01-01T12:00:00Z --to America/Sao_Paulo"
2136
+ ])
2137
+ );
2138
+ date.command("now").description("Print the current date and time.").action(() => {
2139
+ printValue(currentDateTime());
2140
+ });
2141
+ date.command("format").description("Format a date using a token pattern.").requiredOption("--date <value>", "Input date/time value.").requiredOption("--pattern <value>", "Formatting pattern.").action((options) => {
2142
+ printValue(formatDateValue(options.date, options.pattern));
2143
+ });
2144
+ date.command("parse").description("Parse a date string and return normalized metadata.").requiredOption("--date <value>", "Input date/time value.").action((options) => {
2145
+ printValue(parseDateValue(options.date));
2146
+ });
2147
+ date.command("add").description("Add time to a date.").requiredOption("--date <value>", "Input date/time value.").option("--days <number>", "Day delta.", parsePositiveInteger).option("--hours <number>", "Hour delta.", parsePositiveInteger).option("--minutes <number>", "Minute delta.", parsePositiveInteger).option("--seconds <number>", "Second delta.", parsePositiveInteger).action(
2148
+ (options) => {
2149
+ printValue(addToDate(options.date, options));
2150
+ }
2151
+ );
2152
+ date.command("sub").description("Subtract time from a date.").requiredOption("--date <value>", "Input date/time value.").option("--days <number>", "Day delta.", parsePositiveInteger).option("--hours <number>", "Hour delta.", parsePositiveInteger).option("--minutes <number>", "Minute delta.", parsePositiveInteger).option("--seconds <number>", "Second delta.", parsePositiveInteger).action(
2153
+ (options) => {
2154
+ printValue(subtractFromDate(options.date, options));
2155
+ }
2156
+ );
2157
+ date.command("diff").description("Calculate the difference between two dates.").requiredOption("--from <value>", "Source date/time value.").requiredOption("--to <value>", "Target date/time value.").option(
2158
+ "--unit <name>",
2159
+ "Output unit: seconds, minutes, hours or days.",
2160
+ parseDateDiffUnit,
2161
+ "seconds"
2162
+ ).action((options) => {
2163
+ printValue(diffDates(options.from, options.to, options.unit));
2164
+ });
2165
+ date.command("unix").argument("[value]", "Unix seconds or milliseconds to convert to ISO.").description("Convert to or from Unix timestamps.").option("--date <value>", "Date/time value to convert to Unix.").action((value, options) => {
2166
+ if (options.date !== void 0) {
2167
+ printValue(convertDateToUnix(options.date));
2168
+ return;
2169
+ }
2170
+ if (value === void 0) {
2171
+ throw new Error("Provide either a positional unix value or --date.");
2172
+ }
2173
+ printValue(convertUnixValue(value));
2174
+ });
2175
+ date.command("tz").description("Convert a date/time to a target time zone representation.").requiredOption("--from <value>", "Source date/time value.").requiredOption(
2176
+ "--to <value>",
2177
+ "Target IANA time zone, such as America/Sao_Paulo."
2178
+ ).action((options) => {
2179
+ printValue(convertDateToTimeZone(options.from, options.to));
2180
+ });
2181
+ }
2182
+
2183
+ // src/core/utils/seeded-random.ts
2184
+ function createRandomSource(seed) {
2185
+ if (seed === void 0) {
2186
+ return Math.random;
2187
+ }
2188
+ let state = Math.trunc(seed) >>> 0;
2189
+ return () => {
2190
+ state = state + 1831565813 >>> 0;
2191
+ let value = state;
2192
+ value = Math.imul(value ^ value >>> 15, value | 1);
2193
+ value ^= value + Math.imul(value ^ value >>> 7, value | 61);
2194
+ return ((value ^ value >>> 14) >>> 0) / 4294967296;
2195
+ };
2196
+ }
2197
+
2198
+ // src/services/number-picker/number-picker.service.ts
2199
+ function resolveMin(min) {
2200
+ return min ?? Number.MIN_SAFE_INTEGER;
2201
+ }
2202
+ function resolveMax(max) {
2203
+ return max ?? Number.MAX_SAFE_INTEGER;
2204
+ }
2205
+ function pickRandomNumber(options = {}) {
2206
+ const min = resolveMin(options.min);
2207
+ const max = resolveMax(options.max);
2208
+ const randomSource = createRandomSource(options.seed);
2209
+ if (!Number.isInteger(min) || !Number.isInteger(max)) {
2210
+ throw new BRUtilsError("Minimum and maximum values must be integers.");
2211
+ }
2212
+ if (min > max) {
2213
+ throw new BRUtilsError(
2214
+ "Minimum value cannot be greater than maximum value."
2215
+ );
2216
+ }
2217
+ return Math.floor(randomSource() * (max - min + 1)) + min;
2218
+ }
2219
+
2220
+ // src/services/random-number/random-number.generator.ts
2221
+ function resolveIntegerMin(min) {
2222
+ return min ?? Number.MIN_SAFE_INTEGER;
2223
+ }
2224
+ function resolveIntegerMax(max) {
2225
+ return max ?? Number.MAX_SAFE_INTEGER;
2226
+ }
2227
+ function resolveFloatMin(min) {
2228
+ return min ?? 0;
2229
+ }
2230
+ function resolveFloatMax(max) {
2231
+ return max ?? 1;
2232
+ }
2233
+ function randomIntegerBetween(min, max, randomSource) {
2234
+ return Math.floor(randomSource() * (max - min + 1)) + min;
2235
+ }
2236
+ function randomFloatBetween(min, max, randomSource) {
2237
+ return randomSource() * (max - min) + min;
2238
+ }
2239
+ function validateRange(min, max, integerOnly = true) {
2240
+ if (integerOnly && (!Number.isInteger(min) || !Number.isInteger(max))) {
2241
+ throw new BRUtilsError("Minimum and maximum values must be integers.");
2242
+ }
2243
+ if (min > max) {
2244
+ throw new BRUtilsError(
2245
+ "Minimum value cannot be greater than maximum value."
2246
+ );
2247
+ }
2248
+ }
2249
+ function validateCount(count) {
2250
+ if (!Number.isInteger(count) || count < 1) {
2251
+ throw new BRUtilsError("Count must be a positive integer.");
2252
+ }
2253
+ }
2254
+ function normalizePrecision(precision) {
2255
+ if (precision === void 0) {
2256
+ return void 0;
2257
+ }
2258
+ if (!Number.isInteger(precision) || precision < 0) {
2259
+ throw new BRUtilsError("Precision must be a non-negative integer.");
2260
+ }
2261
+ return precision;
2262
+ }
2263
+ function normalizeItems(items) {
2264
+ const normalized = items.map((item) => item.trim()).filter(Boolean);
2265
+ if (normalized.length === 0) {
2266
+ throw new BRUtilsError("At least one item must be provided.");
2267
+ }
2268
+ return normalized;
2269
+ }
2270
+ function applyFloatPrecision(value, precision) {
2271
+ if (precision === void 0) {
2272
+ return value;
2273
+ }
2274
+ return Number(value.toFixed(precision));
2275
+ }
2276
+ function generateRandomIntegers(options = {}) {
2277
+ const min = resolveIntegerMin(options.min);
2278
+ const max = resolveIntegerMax(options.max);
2279
+ const count = options.count ?? 1;
2280
+ const unique = options.unique ?? false;
2281
+ const sorted = options.sorted ?? false;
2282
+ const randomSource = createRandomSource(options.seed);
2283
+ validateRange(min, max, true);
2284
+ validateCount(count);
2285
+ const availableNumbers = max - min + 1;
2286
+ if (unique && count > availableNumbers) {
2287
+ throw new BRUtilsError(
2288
+ "Cannot generate more unique numbers than the available range size."
2289
+ );
2290
+ }
2291
+ let result;
2292
+ if (unique) {
2293
+ const pool = Array.from(
2294
+ { length: availableNumbers },
2295
+ (_, index) => min + index
2296
+ );
2297
+ result = shuffleArray(pool, randomSource).slice(0, count);
2298
+ } else {
2299
+ result = Array.from(
2300
+ { length: count },
2301
+ () => randomIntegerBetween(min, max, randomSource)
2302
+ );
2303
+ }
2304
+ if (sorted) {
2305
+ result.sort((a, b) => a - b);
2306
+ }
2307
+ return result;
2308
+ }
2309
+ function generateRandomFloats(options = {}) {
2310
+ const min = resolveFloatMin(options.min);
2311
+ const max = resolveFloatMax(options.max);
2312
+ const count = options.count ?? 1;
2313
+ const sorted = options.sorted ?? false;
2314
+ const precision = normalizePrecision(options.precision);
2315
+ const randomSource = createRandomSource(options.seed);
2316
+ if (!Number.isFinite(min) || !Number.isFinite(max)) {
2317
+ throw new BRUtilsError(
2318
+ "Minimum and maximum values must be finite numbers."
2319
+ );
2320
+ }
2321
+ validateRange(min, max, false);
2322
+ validateCount(count);
2323
+ const result = Array.from(
2324
+ { length: count },
2325
+ () => applyFloatPrecision(randomFloatBetween(min, max, randomSource), precision)
2326
+ );
2327
+ if (sorted) {
2328
+ result.sort((a, b) => a - b);
2329
+ }
2330
+ return result;
2331
+ }
2332
+ function pickRandomItems(options) {
2333
+ const items = normalizeItems(options.items);
2334
+ const count = options.count ?? 1;
2335
+ const unique = options.unique ?? false;
2336
+ const randomSource = createRandomSource(options.seed);
2337
+ validateCount(count);
2338
+ if (unique && count > items.length) {
2339
+ throw new BRUtilsError(
2340
+ "Cannot pick more unique items than the available item count."
2341
+ );
2342
+ }
2343
+ if (unique) {
2344
+ return shuffleArray(items, randomSource).slice(0, count);
2345
+ }
2346
+ return Array.from(
2347
+ { length: count },
2348
+ () => pickRandomItem(items, randomSource)
2349
+ );
2350
+ }
2351
+ function shuffleRandomItems(options) {
2352
+ return shuffleArray(
2353
+ normalizeItems(options.items),
2354
+ createRandomSource(options.seed)
2355
+ );
2356
+ }
2357
+ function rollDice(options = {}) {
2358
+ const faces = options.faces ?? 6;
2359
+ const count = options.count ?? 1;
2360
+ const randomSource = createRandomSource(options.seed);
2361
+ if (!Number.isInteger(faces) || faces < 2) {
2362
+ throw new BRUtilsError(
2363
+ "Dice faces must be an integer greater than or equal to 2."
2364
+ );
2365
+ }
2366
+ validateCount(count);
2367
+ return Array.from(
2368
+ { length: count },
2369
+ () => randomIntegerBetween(1, faces, randomSource)
2370
+ );
2371
+ }
2372
+ function flipCoin(options = {}) {
2373
+ const randomSource = createRandomSource(options.seed);
2374
+ return randomSource() < 0.5 ? "heads" : "tails";
2375
+ }
2376
+
2377
+ // src/cli/shared/io.ts
2378
+ import fs6 from "fs";
2379
+
2380
+ // src/services/json/json.service.ts
2381
+ function isPlainObject(value) {
2382
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2383
+ }
2384
+ function cloneJsonValue(value) {
2385
+ return JSON.parse(JSON.stringify(value));
2386
+ }
2387
+ function sortKeysDeep(value) {
2388
+ if (Array.isArray(value)) {
2389
+ return value.map((item) => sortKeysDeep(item));
2390
+ }
2391
+ if (isPlainObject(value)) {
2392
+ return Object.keys(value).sort((left, right) => left.localeCompare(right)).reduce((accumulator, key) => {
2393
+ accumulator[key] = sortKeysDeep(value[key]);
2394
+ return accumulator;
2395
+ }, {});
2396
+ }
2397
+ return value;
2398
+ }
2399
+ function formatYamlScalar(value) {
2400
+ if (value === null) {
2401
+ return "null";
2402
+ }
2403
+ if (typeof value === "number" || typeof value === "boolean") {
2404
+ return String(value);
2405
+ }
2406
+ if (typeof value === "string") {
2407
+ if (/^[A-Za-z0-9_-]+$/.test(value)) {
2408
+ return value;
2409
+ }
2410
+ return JSON.stringify(value);
2411
+ }
2412
+ return JSON.stringify(value);
2413
+ }
2414
+ function toYamlLines(value, depth = 0) {
2415
+ const indent = " ".repeat(depth);
2416
+ if (Array.isArray(value)) {
2417
+ if (value.length === 0) {
2418
+ return [`${indent}[]`];
2419
+ }
2420
+ return value.flatMap((item) => {
2421
+ if (Array.isArray(item) || isPlainObject(item)) {
2422
+ const nested = toYamlLines(item, depth + 1);
2423
+ return [`${indent}-`, ...nested];
2424
+ }
2425
+ return [`${indent}- ${formatYamlScalar(item)}`];
2426
+ });
2427
+ }
2428
+ if (isPlainObject(value)) {
2429
+ const keys = Object.keys(value);
2430
+ if (keys.length === 0) {
2431
+ return [`${indent}{}`];
2432
+ }
2433
+ return keys.flatMap((key) => {
2434
+ const child = value[key];
2435
+ if (Array.isArray(child) || isPlainObject(child)) {
2436
+ return [`${indent}${key}:`, ...toYamlLines(child, depth + 1)];
2437
+ }
2438
+ return [`${indent}${key}: ${formatYamlScalar(child)}`];
2439
+ });
2440
+ }
2441
+ return [`${indent}${formatYamlScalar(value)}`];
2442
+ }
2443
+ function parsePath(path11) {
2444
+ const segments = path11.match(/[^.[\]]+/g) ?? [];
2445
+ if (segments.length === 0) {
2446
+ throw new BRUtilsError("JSON path cannot be empty.");
2447
+ }
2448
+ return segments;
2449
+ }
2450
+ function getContainerForPath(root, segments) {
2451
+ let current = root;
2452
+ for (const segment of segments) {
2453
+ if (Array.isArray(current)) {
2454
+ const index = Number(segment);
2455
+ if (!Number.isInteger(index)) {
2456
+ return void 0;
2457
+ }
2458
+ current = current[index];
2459
+ continue;
2460
+ }
2461
+ if (isPlainObject(current)) {
2462
+ current = current[segment];
2463
+ continue;
2464
+ }
2465
+ return void 0;
2466
+ }
2467
+ return current;
2468
+ }
2469
+ function appendDiffPath(basePath, segment) {
2470
+ if (typeof segment === "number") {
2471
+ return `${basePath}[${segment}]`;
2472
+ }
2473
+ return basePath === "$" ? `$.${segment}` : `${basePath}.${segment}`;
2474
+ }
2475
+ function deepEqual(left, right) {
2476
+ return JSON.stringify(left) === JSON.stringify(right);
2477
+ }
2478
+ function diffValues(left, right, basePath = "$") {
2479
+ if (deepEqual(left, right)) {
2480
+ return [];
2481
+ }
2482
+ if (Array.isArray(left) && Array.isArray(right)) {
2483
+ const result = [];
2484
+ const maxLength = Math.max(left.length, right.length);
2485
+ for (let index = 0; index < maxLength; index += 1) {
2486
+ if (index >= left.length) {
2487
+ result.push({
2488
+ path: appendDiffPath(basePath, index),
2489
+ type: "added",
2490
+ right: right[index]
2491
+ });
2492
+ continue;
2493
+ }
2494
+ if (index >= right.length) {
2495
+ result.push({
2496
+ path: appendDiffPath(basePath, index),
2497
+ type: "removed",
2498
+ left: left[index]
2499
+ });
2500
+ continue;
2501
+ }
2502
+ result.push(
2503
+ ...diffValues(
2504
+ left[index],
2505
+ right[index],
2506
+ appendDiffPath(basePath, index)
2507
+ )
2508
+ );
2509
+ }
2510
+ return result;
2511
+ }
2512
+ if (isPlainObject(left) && isPlainObject(right)) {
2513
+ const result = [];
2514
+ const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
2515
+ for (const key of Array.from(keys).sort((a, b) => a.localeCompare(b))) {
2516
+ if (!(key in left)) {
2517
+ result.push({
2518
+ path: appendDiffPath(basePath, key),
2519
+ type: "added",
2520
+ right: right[key]
2521
+ });
2522
+ continue;
2523
+ }
2524
+ if (!(key in right)) {
2525
+ result.push({
2526
+ path: appendDiffPath(basePath, key),
2527
+ type: "removed",
2528
+ left: left[key]
2529
+ });
2530
+ continue;
2531
+ }
2532
+ result.push(
2533
+ ...diffValues(left[key], right[key], appendDiffPath(basePath, key))
2534
+ );
2535
+ }
2536
+ return result;
2537
+ }
2538
+ return [
2539
+ {
2540
+ path: basePath,
2541
+ type: "changed",
2542
+ left,
2543
+ right
2544
+ }
2545
+ ];
2546
+ }
2547
+ function deepMerge(left, right) {
2548
+ if (Array.isArray(left) && Array.isArray(right)) {
2549
+ return cloneJsonValue(right);
2550
+ }
2551
+ if (isPlainObject(left) && isPlainObject(right)) {
2552
+ const result = cloneJsonValue(left);
2553
+ for (const [key, value] of Object.entries(right)) {
2554
+ if (key in result) {
2555
+ result[key] = deepMerge(result[key], value);
2556
+ } else {
2557
+ result[key] = cloneJsonValue(value);
2558
+ }
2559
+ }
2560
+ return result;
2561
+ }
2562
+ return cloneJsonValue(right);
2563
+ }
2564
+ function parseJsonInput(value) {
2565
+ try {
2566
+ return JSON.parse(value);
2567
+ } catch (error) {
2568
+ const message = error instanceof Error ? error.message : "Unknown JSON parse error.";
2569
+ throw new BRUtilsError(message);
2570
+ }
2571
+ }
2572
+ function validateJsonInput(value) {
2573
+ try {
2574
+ JSON.parse(value);
2575
+ return { isValid: true };
2576
+ } catch (error) {
2577
+ const message = error instanceof Error ? error.message : "Unknown JSON parse error.";
2578
+ return { isValid: false, error: message };
2579
+ }
2580
+ }
2581
+ function formatJsonValue(value, indent = 2, sortKeys = false) {
2582
+ if (!Number.isInteger(indent) || indent < 0) {
2583
+ throw new BRUtilsError("Indent must be a non-negative integer.");
2584
+ }
2585
+ const normalized = sortKeys ? sortKeysDeep(value) : value;
2586
+ return JSON.stringify(normalized, null, indent);
2587
+ }
2588
+ function minifyJsonValue(value) {
2589
+ return JSON.stringify(value);
2590
+ }
2591
+ function getJsonPathValue(value, path11) {
2592
+ return getContainerForPath(value, parsePath(path11));
2593
+ }
2594
+ function setJsonPathValue(value, path11, newValue) {
2595
+ const root = cloneJsonValue(value);
2596
+ const segments = parsePath(path11);
2597
+ let current = root;
2598
+ segments.forEach((segment, index) => {
2599
+ const isLast = index === segments.length - 1;
2600
+ const nextSegment = segments[index + 1];
2601
+ const nextShouldBeArray = nextSegment !== void 0 && /^\d+$/.test(nextSegment);
2602
+ if (Array.isArray(current)) {
2603
+ const currentIndex = Number(segment);
2604
+ if (!Number.isInteger(currentIndex)) {
2605
+ throw new BRUtilsError(`Invalid array index in path: ${segment}`);
2606
+ }
2607
+ if (isLast) {
2608
+ current[currentIndex] = newValue;
2609
+ return;
2610
+ }
2611
+ if (current[currentIndex] === void 0) {
2612
+ current[currentIndex] = nextShouldBeArray ? [] : {};
2613
+ }
2614
+ current = current[currentIndex];
2615
+ return;
2616
+ }
2617
+ if (!isPlainObject(current)) {
2618
+ throw new BRUtilsError(`Cannot set nested path at segment: ${segment}`);
2619
+ }
2620
+ if (isLast) {
2621
+ current[segment] = newValue;
2622
+ return;
2623
+ }
2624
+ if (current[segment] === void 0) {
2625
+ current[segment] = nextShouldBeArray ? [] : {};
2626
+ }
2627
+ current = current[segment];
2628
+ });
2629
+ return root;
2630
+ }
2631
+ function deleteJsonPathValue(value, path11) {
2632
+ const root = cloneJsonValue(value);
2633
+ const segments = parsePath(path11);
2634
+ const parent = getContainerForPath(root, segments.slice(0, -1));
2635
+ const lastSegment = segments[segments.length - 1];
2636
+ if (Array.isArray(parent)) {
2637
+ const index = Number(lastSegment);
2638
+ if (!Number.isInteger(index)) {
2639
+ throw new BRUtilsError(`Invalid array index in path: ${lastSegment}`);
2640
+ }
2641
+ parent.splice(index, 1);
2642
+ return root;
2643
+ }
2644
+ if (isPlainObject(parent)) {
2645
+ delete parent[lastSegment];
2646
+ return root;
2647
+ }
2648
+ if (segments.length === 1 && isPlainObject(root)) {
2649
+ delete root[lastSegment];
2650
+ return root;
2651
+ }
2652
+ throw new BRUtilsError(`Path not found: ${path11}`);
2653
+ }
2654
+ function diffJsonValues(left, right) {
2655
+ const changes = diffValues(left, right);
2656
+ return {
2657
+ isEqual: changes.length === 0,
2658
+ changes
2659
+ };
2660
+ }
2661
+ function mergeJsonValues(values) {
2662
+ if (values.length < 2) {
2663
+ throw new BRUtilsError("Merge requires at least two JSON sources.");
2664
+ }
2665
+ return values.slice(1).reduce((accumulator, current) => {
2666
+ return deepMerge(accumulator, current);
2667
+ }, cloneJsonValue(values[0]));
2668
+ }
2669
+ function convertJsonToYaml(value) {
2670
+ return toYamlLines(value).join("\n");
2671
+ }
2672
+
2673
+ // src/cli/shared/io.ts
2674
+ function readTextSource(options) {
2675
+ if (options.text && options.file) {
2676
+ throw new Error("Use either --text or --file, not both.");
2677
+ }
2678
+ if (options.text !== void 0) {
2679
+ return options.text;
2680
+ }
2681
+ if (options.file) {
2682
+ return fs6.readFileSync(options.file, "utf-8");
2683
+ }
2684
+ throw new Error("One of --text or --file is required.");
2685
+ }
2686
+ function normalizeStringList(value) {
2687
+ if (value === void 0) {
2688
+ return [];
2689
+ }
2690
+ return Array.isArray(value) ? value : [value];
2691
+ }
2692
+ function readSingleJsonSource(options) {
2693
+ if (options.file && options.value) {
2694
+ throw new Error("Use either --file or --value, not both.");
2695
+ }
2696
+ if (options.file) {
2697
+ const raw = fs6.readFileSync(options.file, "utf-8");
2698
+ return {
2699
+ sourcePath: options.file,
2700
+ raw,
2701
+ parsed: parseJsonInput(raw)
2702
+ };
2703
+ }
2704
+ if (options.value !== void 0) {
2705
+ return {
2706
+ raw: options.value,
2707
+ parsed: parseJsonInput(options.value)
2708
+ };
2709
+ }
2710
+ throw new Error("One of --file or --value is required.");
2711
+ }
2712
+ function readMultipleJsonSources(options) {
2713
+ const fileValues = normalizeStringList(options.file);
2714
+ const inlineValues = normalizeStringList(options.value);
2715
+ if (fileValues.length + inlineValues.length < 2) {
2716
+ throw new Error(
2717
+ "Provide at least two JSON sources via --file and/or --value."
2718
+ );
2719
+ }
2720
+ return [
2721
+ ...fileValues.map((filePath) => {
2722
+ const raw = fs6.readFileSync(filePath, "utf-8");
2723
+ return parseJsonInput(raw);
2724
+ }),
2725
+ ...inlineValues.map((value) => parseJsonInput(value))
2726
+ ];
2727
+ }
2728
+ function readDiffJsonSource(value) {
2729
+ if (fs6.existsSync(value) && fs6.statSync(value).isFile()) {
2730
+ return parseJsonInput(fs6.readFileSync(value, "utf-8"));
2731
+ }
2732
+ return parseJsonInput(value);
2733
+ }
2734
+ function writeTextFile(pathValue, content) {
2735
+ fs6.writeFileSync(pathValue, content, "utf-8");
2736
+ }
2737
+ function readItems(items, file) {
2738
+ if (items && file) {
2739
+ throw new Error("Use either --items or --file, not both.");
2740
+ }
2741
+ if (!items && !file) {
2742
+ throw new Error("One of --items or --file is required.");
2743
+ }
2744
+ if (items) {
2745
+ const parsed2 = items.split(",").map((item) => item.trim()).filter(Boolean);
2746
+ if (parsed2.length === 0) {
2747
+ throw new Error("The --items value did not produce any usable items.");
2748
+ }
2749
+ return parsed2;
2750
+ }
2751
+ const content = fs6.readFileSync(file, "utf-8");
2752
+ const parsed = content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2753
+ if (parsed.length === 0) {
2754
+ throw new Error(
2755
+ "The file passed to --file did not contain any usable items."
2756
+ );
2757
+ }
2758
+ return parsed;
2759
+ }
2760
+
2761
+ // src/cli/commands/register-random.ts
2762
+ function registerRandomCommands(program) {
2763
+ const randomNumber = program.command("random-number").alias("rand").description(
2764
+ "Generate random integers, floats, picks, shuffles, dice rolls and coin flips."
2765
+ ).addHelpText(
2766
+ "after",
2767
+ examples([
2768
+ "brutils random-number int --min 1 --max 100 --count 5 --sorted",
2769
+ "brutils random-number float --min 0 --max 1 --count 3 --precision 4",
2770
+ 'brutils random-number pick --items "red,blue,green" --count 2 --unique',
2771
+ "brutils random-number shuffle --file ./items.txt",
2772
+ "brutils random-number dice --faces 20 --count 2",
2773
+ "brutils random-number coin --seed 42"
2774
+ ])
2775
+ );
2776
+ randomNumber.command("int").alias("generate").description("Generate random integers within a range.").option("--min <number>", "Minimum integer value.", parseInteger).option("--max <number>", "Maximum integer value.", parseInteger).option(
2777
+ "--count <number>",
2778
+ "How many values to generate.",
2779
+ parsePositiveInteger,
2780
+ 1
2781
+ ).option("--unique", "Avoid duplicates when generating multiple values.").option("--sorted", "Sort the generated values in ascending order.").option(
2782
+ "--seed <number>",
2783
+ "Seed used for deterministic output.",
2784
+ parseInteger
2785
+ ).option(
2786
+ "--format <mode>",
2787
+ "Output mode: plain, json or csv.",
2788
+ parseRandomFormat,
2789
+ "plain"
2790
+ ).action(
2791
+ (options) => {
2792
+ printRandomValues(generateRandomIntegers(options), options.format);
2793
+ }
2794
+ );
2795
+ randomNumber.command("float").description("Generate random floating-point numbers within a range.").option("--min <number>", "Minimum numeric value.", parseNumber).option("--max <number>", "Maximum numeric value.", parseNumber).option(
2796
+ "--count <number>",
2797
+ "How many values to generate.",
2798
+ parsePositiveInteger,
2799
+ 1
2800
+ ).option("--sorted", "Sort the generated values in ascending order.").option(
2801
+ "--precision <number>",
2802
+ "Number of decimal places to preserve.",
2803
+ parsePositiveInteger
2804
+ ).option(
2805
+ "--seed <number>",
2806
+ "Seed used for deterministic output.",
2807
+ parseInteger
2808
+ ).option(
2809
+ "--format <mode>",
2810
+ "Output mode: plain, json or csv.",
2811
+ parseRandomFormat,
2812
+ "plain"
2813
+ ).action(
2814
+ (options) => {
2815
+ printRandomValues(generateRandomFloats(options), options.format);
2816
+ }
2817
+ );
2818
+ randomNumber.command("pick").description("Pick one or more items from a list.").option("--items <csv>", "Comma-separated items to pick from.").option(
2819
+ "--file <path>",
2820
+ "Read source items from a text file (one per line)."
2821
+ ).option(
2822
+ "--count <number>",
2823
+ "How many items to pick.",
2824
+ parsePositiveInteger,
2825
+ 1
2826
+ ).option("--unique", "Avoid duplicate picks when possible.").option(
2827
+ "--seed <number>",
2828
+ "Seed used for deterministic output.",
2829
+ parseInteger
2830
+ ).option(
2831
+ "--format <mode>",
2832
+ "Output mode: plain, json or csv.",
2833
+ parseRandomFormat,
2834
+ "plain"
2835
+ ).action(
2836
+ (options) => {
2837
+ const values = pickRandomItems({
2838
+ items: readItems(options.items, options.file),
2839
+ count: options.count,
2840
+ ...options.unique !== void 0 ? { unique: options.unique } : {},
2841
+ ...options.seed !== void 0 ? { seed: options.seed } : {}
2842
+ });
2843
+ printRandomValues(values, options.format);
2844
+ }
2845
+ );
2846
+ randomNumber.command("shuffle").description("Shuffle a list of items.").option("--items <csv>", "Comma-separated items to shuffle.").option(
2847
+ "--file <path>",
2848
+ "Read source items from a text file (one per line)."
2849
+ ).option(
2850
+ "--seed <number>",
2851
+ "Seed used for deterministic output.",
2852
+ parseInteger
2853
+ ).option(
2854
+ "--format <mode>",
2855
+ "Output mode: plain, json or csv.",
2856
+ parseRandomFormat,
2857
+ "plain"
2858
+ ).action(
2859
+ (options) => {
2860
+ const values = shuffleRandomItems({
2861
+ items: readItems(options.items, options.file),
2862
+ ...options.seed !== void 0 ? { seed: options.seed } : {}
2863
+ });
2864
+ printRandomValues(values, options.format);
2865
+ }
2866
+ );
2867
+ randomNumber.command("dice").description("Roll one or more dice.").option(
2868
+ "--faces <number>",
2869
+ "Number of faces on the die.",
2870
+ parsePositiveInteger,
2871
+ 6
2872
+ ).option(
2873
+ "--count <number>",
2874
+ "How many rolls to generate.",
2875
+ parsePositiveInteger,
2876
+ 1
2877
+ ).option(
2878
+ "--seed <number>",
2879
+ "Seed used for deterministic output.",
2880
+ parseInteger
2881
+ ).option(
2882
+ "--format <mode>",
2883
+ "Output mode: plain, json or csv.",
2884
+ parseRandomFormat,
2885
+ "plain"
2886
+ ).action(
2887
+ (options) => {
2888
+ printRandomValues(rollDice(options), options.format);
2889
+ }
2890
+ );
2891
+ randomNumber.command("coin").description("Flip a coin once.").option(
2892
+ "--seed <number>",
2893
+ "Seed used for deterministic output.",
2894
+ parseInteger
2895
+ ).action((options) => {
2896
+ printValue(flipCoin(options));
2897
+ });
2898
+ const numberPicker = program.command("number-picker").alias("pick-number").description("Pick a single random integer within a range.").addHelpText(
2899
+ "after",
2900
+ examples([
2901
+ "brutils number-picker run --min 1 --max 10",
2902
+ "brutils number-picker run --min 100 --max 999 --seed 42"
2903
+ ])
2904
+ );
2905
+ numberPicker.command("run").description("Pick one random integer.").option("--min <number>", "Minimum integer value.", parseInteger).option("--max <number>", "Maximum integer value.", parseInteger).option(
2906
+ "--seed <number>",
2907
+ "Seed used for deterministic output.",
2908
+ parseInteger
2909
+ ).action((options) => {
2910
+ printValue(pickRandomNumber(options));
2911
+ });
2912
+ }
2913
+
2914
+ // src/services/str/str.service.ts
2915
+ function ensureNonNegativeInteger(value, label) {
2916
+ if (!Number.isInteger(value) || value < 0) {
2917
+ throw new BRUtilsError(`${label} must be a non-negative integer.`);
2918
+ }
2919
+ }
2920
+ function splitWords(value) {
2921
+ return value.replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/[_\-.]+/g, " ").trim().split(/\s+/).filter(Boolean).map((word) => word.toLowerCase());
2922
+ }
2923
+ function capitalize(word) {
2924
+ if (word.length === 0) {
2925
+ return word;
2926
+ }
2927
+ return word[0].toUpperCase() + word.slice(1).toLowerCase();
2928
+ }
2929
+ function encodeHtmlEntities(value) {
2930
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2931
+ }
2932
+ function decodeHtmlEntities(value) {
2933
+ return value.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
2934
+ }
2935
+ function buildGlobalRegex(pattern) {
2936
+ try {
2937
+ return new RegExp(pattern, "g");
2938
+ } catch {
2939
+ throw new BRUtilsError(`Invalid regex pattern: ${pattern}`);
2940
+ }
2941
+ }
2942
+ function extractUsingDelimiters(value, query) {
2943
+ const separatorIndex = query.indexOf("|");
2944
+ if (separatorIndex === -1) {
2945
+ throw new BRUtilsError(
2946
+ 'Delimiter extraction expects a query in the format "start|end".'
2947
+ );
2948
+ }
2949
+ const startDelimiter = query.slice(0, separatorIndex);
2950
+ const endDelimiter = query.slice(separatorIndex + 1);
2951
+ if (startDelimiter.length === 0 || endDelimiter.length === 0) {
2952
+ throw new BRUtilsError(
2953
+ "Delimiter extraction expects non-empty start and end delimiters."
2954
+ );
2955
+ }
2956
+ const matches = [];
2957
+ let cursor = 0;
2958
+ while (cursor < value.length) {
2959
+ const startIndex = value.indexOf(startDelimiter, cursor);
2960
+ if (startIndex === -1) {
2961
+ break;
2962
+ }
2963
+ const contentStart = startIndex + startDelimiter.length;
2964
+ const endIndex = value.indexOf(endDelimiter, contentStart);
2965
+ if (endIndex === -1) {
2966
+ break;
2967
+ }
2968
+ matches.push(value.slice(contentStart, endIndex));
2969
+ cursor = endIndex + endDelimiter.length;
2970
+ }
2971
+ return matches;
2972
+ }
2973
+ function slugifyText(value) {
2974
+ return removeAccents(value).toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
2975
+ }
2976
+ function convertStringCase(value, style) {
2977
+ const words = splitWords(value);
2978
+ if (words.length === 0) {
2979
+ return "";
2980
+ }
2981
+ switch (style) {
2982
+ case "camel":
2983
+ return words[0] + words.slice(1).map(capitalize).join("");
2984
+ case "snake":
2985
+ return words.join("_");
2986
+ case "kebab":
2987
+ return words.join("-");
2988
+ case "pascal":
2989
+ return words.map(capitalize).join("");
2990
+ case "constant":
2991
+ return words.join("_").toUpperCase();
2992
+ case "title":
2993
+ return words.map(capitalize).join(" ");
2994
+ }
2995
+ }
2996
+ function trimText(value) {
2997
+ return value.trim();
2998
+ }
2999
+ function truncateText(value, options) {
3000
+ ensureNonNegativeInteger(options.max, "Maximum length");
3001
+ if (value.length <= options.max) {
3002
+ return value;
3003
+ }
3004
+ const suffix = options.suffix ?? "";
3005
+ if (suffix.length >= options.max) {
3006
+ return suffix.slice(0, options.max);
3007
+ }
3008
+ return value.slice(0, options.max - suffix.length) + suffix;
3009
+ }
3010
+ function replaceText(value, options) {
3011
+ if (options.regex) {
3012
+ return value.replace(buildGlobalRegex(options.from), options.with);
3013
+ }
3014
+ return value.replaceAll(options.from, options.with);
3015
+ }
3016
+ function normalizeText(value) {
3017
+ return value.normalize("NFC");
3018
+ }
3019
+ function removeAccents(value) {
3020
+ return value.normalize("NFD").replace(/[̀-ͯ]/g, "");
3021
+ }
3022
+ function padText(value, options) {
3023
+ ensureNonNegativeInteger(options.length, "Target length");
3024
+ const side = options.side ?? "right";
3025
+ if (value.length >= options.length) {
3026
+ return value;
3027
+ }
3028
+ switch (side) {
3029
+ case "left":
3030
+ return value.padStart(options.length);
3031
+ case "right":
3032
+ return value.padEnd(options.length);
3033
+ case "both": {
3034
+ const totalPadding = options.length - value.length;
3035
+ const leftPadding = Math.floor(totalPadding / 2);
3036
+ const rightPadding = totalPadding - leftPadding;
3037
+ return `${" ".repeat(leftPadding)}${value}${" ".repeat(rightPadding)}`;
3038
+ }
3039
+ }
3040
+ }
3041
+ function extractText(value, query, options = {}) {
3042
+ if (options.regex) {
3043
+ const matches = Array.from(value.matchAll(buildGlobalRegex(query)));
3044
+ return matches.flatMap((match) => {
3045
+ const groups = match.slice(1).filter((group) => group !== void 0);
3046
+ return groups.length > 0 ? groups : [match[0]];
3047
+ });
3048
+ }
3049
+ return extractUsingDelimiters(value, query);
3050
+ }
3051
+ function transformBase64(value, mode = "encode") {
3052
+ if (mode === "decode") {
3053
+ return Buffer.from(value, "base64").toString("utf-8");
3054
+ }
3055
+ return Buffer.from(value, "utf-8").toString("base64");
3056
+ }
3057
+ function transformUrlEncoding(value, mode = "encode") {
3058
+ if (mode === "decode") {
3059
+ return decodeURIComponent(value);
3060
+ }
3061
+ return encodeURIComponent(value);
3062
+ }
3063
+ function transformHtmlEntities(value, mode = "encode") {
3064
+ if (mode === "decode") {
3065
+ return decodeHtmlEntities(value);
3066
+ }
3067
+ return encodeHtmlEntities(value);
3068
+ }
3069
+ function getLevenshteinDistance(a, b) {
3070
+ a = a.trim();
3071
+ b = b.trim();
3072
+ const n = a.length;
3073
+ const m = b.length;
3074
+ const dp = Array.from(
3075
+ { length: n + 1 },
3076
+ () => new Array(m + 1).fill(0)
3077
+ );
3078
+ for (let i = 0; i <= n; i++) dp[i][0] = i;
3079
+ for (let j = 0; j <= m; j++) dp[0][j] = j;
3080
+ for (let i = 1; i <= n; i++) {
3081
+ for (let j = 1; j <= m; j++) {
3082
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
3083
+ dp[i][j] = Math.min(
3084
+ dp[i - 1][j] + 1,
3085
+ dp[i][j - 1] + 1,
3086
+ dp[i - 1][j - 1] + cost
3087
+ );
3088
+ }
3089
+ }
3090
+ return dp[n][m];
3091
+ }
3092
+
3093
+ // src/cli/commands/register-text-data.ts
3094
+ function registerTextDataCommands(program) {
3095
+ const str = program.command("str").description("String transformations and encoding helpers.").addHelpText(
3096
+ "after",
3097
+ examples([
3098
+ 'brutils str slug --text "Hello Cool World"',
3099
+ 'brutils str case --text "my cool variable" --to camel',
3100
+ 'brutils str truncate --text "hello world" --max 8 --suffix "..."',
3101
+ 'brutils str replace --text "hello 123" --from "\\\\d+" --with "X" --regex',
3102
+ 'brutils str extract "\\\\[(.*?)\\\\]" --text "[one] [two]" --regex',
3103
+ 'brutils str base64 --text "hello" --mode encode',
3104
+ 'brutils str leven "kitten" "sitting"'
3105
+ ])
3106
+ );
3107
+ str.command("slug").description("Convert text to a URL-friendly slug.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").action((options) => {
3108
+ printValue(slugifyText(readTextSource(options)));
3109
+ });
3110
+ str.command("case").description("Convert text between casing styles.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").requiredOption(
3111
+ "--to <style>",
3112
+ "Target style: camel, snake, kebab, pascal, constant or title.",
3113
+ parseStringCaseStyle
3114
+ ).action(
3115
+ (options) => {
3116
+ printValue(convertStringCase(readTextSource(options), options.to));
3117
+ }
3118
+ );
3119
+ str.command("trim").description("Remove surrounding spaces and newlines.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").action((options) => {
3120
+ printValue(trimText(readTextSource(options)));
3121
+ });
3122
+ str.command("truncate").description("Cut text to a maximum length.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").requiredOption("--max <number>", "Maximum length.", parsePositiveInteger).option("--suffix <value>", "Suffix to append after truncation.").action(
3123
+ (options) => {
3124
+ printValue(
3125
+ truncateText(readTextSource(options), {
3126
+ max: options.max,
3127
+ ...options.suffix !== void 0 ? { suffix: options.suffix } : {}
3128
+ })
3129
+ );
3130
+ }
3131
+ );
3132
+ str.command("replace").description("Replace text fragments or regex matches.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").requiredOption("--from <value>", "Text or regex pattern to replace.").requiredOption("--with <value>", "Replacement value.").option("--regex", "Interpret --from as a regex pattern.").action(
3133
+ (options) => {
3134
+ printValue(
3135
+ replaceText(readTextSource(options), {
3136
+ from: options.from,
3137
+ with: options.with,
3138
+ ...options.regex !== void 0 ? { regex: options.regex } : {}
3139
+ })
3140
+ );
3141
+ }
3142
+ );
3143
+ str.command("normalize").description("Normalize text using Unicode NFC.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").action((options) => {
3144
+ printValue(normalizeText(readTextSource(options)));
3145
+ });
3146
+ str.command("remove-accents").description("Remove accents and diacritics from text.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").action((options) => {
3147
+ printValue(removeAccents(readTextSource(options)));
3148
+ });
3149
+ str.command("pad").description("Pad a string with spaces on the left, right or both sides.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").requiredOption(
3150
+ "--length <number>",
3151
+ "Target text length.",
3152
+ parsePositiveInteger
3153
+ ).option(
3154
+ "--side <side>",
3155
+ "Pad direction: left, right or both.",
3156
+ parseStringPadSide,
3157
+ "right"
3158
+ ).action(
3159
+ (options) => {
3160
+ printValue(
3161
+ padText(readTextSource(options), {
3162
+ length: options.length,
3163
+ side: options.side
3164
+ })
3165
+ );
3166
+ }
3167
+ );
3168
+ str.command("extract").argument("<query>", 'Regex pattern or delimiter pair such as "start|end".').description("Extract content by regex or by delimiter pairs.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").option("--regex", "Interpret the query as a regex pattern.").action(
3169
+ (query, options) => {
3170
+ printValue(
3171
+ extractText(readTextSource(options), query, {
3172
+ ...options.regex !== void 0 ? { regex: options.regex } : {}
3173
+ })
3174
+ );
3175
+ }
3176
+ );
3177
+ str.command("base64").description("Encode or decode Base64 values.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").option(
3178
+ "--mode <mode>",
3179
+ "Use encode or decode mode.",
3180
+ parseStringCodecMode,
3181
+ "encode"
3182
+ ).action(
3183
+ (options) => {
3184
+ printValue(transformBase64(readTextSource(options), options.mode));
3185
+ }
3186
+ );
3187
+ str.command("urlencode").description("Encode or decode URL-safe content.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").option(
3188
+ "--mode <mode>",
3189
+ "Use encode or decode mode.",
3190
+ parseStringCodecMode,
3191
+ "encode"
3192
+ ).action(
3193
+ (options) => {
3194
+ printValue(transformUrlEncoding(readTextSource(options), options.mode));
3195
+ }
3196
+ );
3197
+ str.command("html").description("Encode or decode basic HTML entities.").option("--text <value>", "Inline text input.").option("--file <path>", "Read input text from a file.").option(
3198
+ "--mode <mode>",
3199
+ "Use encode or decode mode.",
3200
+ parseStringCodecMode,
3201
+ "encode"
3202
+ ).action(
3203
+ (options) => {
3204
+ printValue(
3205
+ transformHtmlEntities(readTextSource(options), options.mode)
3206
+ );
3207
+ }
3208
+ );
3209
+ str.description("Calculates the Levenshtein distance between two strings").command("leven").argument("<a>").argument("<b>").action((a, b) => {
3210
+ printValue(getLevenshteinDistance(a, b));
3211
+ });
3212
+ const jsonCommand = program.command("json").description("Local JSON formatting, editing and diff helpers.").addHelpText(
3213
+ "after",
3214
+ examples([
3215
+ "brutils json format --file ./config.json --sort-keys",
3216
+ `brutils json validate --value '{"ok":true}'`,
3217
+ "brutils json get --file ./config.json --path server.port",
3218
+ "brutils json set --file ./config.json --path flags.dev --set-value true --in-place",
3219
+ "brutils json diff --left ./a.json --right ./b.json",
3220
+ "brutils json merge --file ./base.json --file ./override.json",
3221
+ `brutils json to-yaml --value '{"name":"brutils"}'`
3222
+ ])
3223
+ );
3224
+ jsonCommand.command("format").description("Pretty-print JSON.").option("--file <path>", "Read JSON from a file.").option("--value <json>", "Read JSON inline.").option(
3225
+ "--indent <number>",
3226
+ "Pretty-print indentation size.",
3227
+ parseInteger,
3228
+ 2
3229
+ ).option("--sort-keys", "Sort object keys recursively before printing.").option("--in-place", "Write the formatted content back to the input file.").action(
3230
+ (options) => {
3231
+ const source = readSingleJsonSource(options);
3232
+ const result = formatJsonValue(
3233
+ source.parsed,
3234
+ options.indent,
3235
+ options.sortKeys ?? false
3236
+ );
3237
+ if (options.inPlace) {
3238
+ if (!source.sourcePath) {
3239
+ throw new Error("--in-place requires --file.");
3240
+ }
3241
+ writeTextFile(source.sourcePath, `${result}
3242
+ `);
3243
+ }
3244
+ printValue(result);
3245
+ }
3246
+ );
3247
+ jsonCommand.command("minify").description("Minify JSON.").option("--file <path>", "Read JSON from a file.").option("--value <json>", "Read JSON inline.").option("--in-place", "Write the minified content back to the input file.").action((options) => {
3248
+ const source = readSingleJsonSource(options);
3249
+ const result = minifyJsonValue(source.parsed);
3250
+ if (options.inPlace) {
3251
+ if (!source.sourcePath) {
3252
+ throw new Error("--in-place requires --file.");
3253
+ }
3254
+ writeTextFile(source.sourcePath, result);
3255
+ }
3256
+ printValue(result);
3257
+ });
3258
+ jsonCommand.command("validate").description("Validate JSON syntax.").option("--file <path>", "Read JSON from a file.").option("--value <json>", "Read JSON inline.").action((options) => {
3259
+ const source = readSingleJsonSource(options);
3260
+ printValue(validateJsonInput(source.raw));
3261
+ });
3262
+ jsonCommand.command("get").description("Read a path from JSON.").option("--file <path>", "Read JSON from a file.").option("--value <json>", "Read JSON inline.").requiredOption("--path <value>", "JSON path to read.").action((options) => {
3263
+ const source = readSingleJsonSource(options);
3264
+ printValue(getJsonPathValue(source.parsed, options.path));
3265
+ });
3266
+ jsonCommand.command("set").description("Write a path in JSON.").option("--file <path>", "Read JSON from a file.").option("--value <json>", "Read JSON inline.").requiredOption("--path <value>", "JSON path to update.").requiredOption(
3267
+ "--set-value <json>",
3268
+ "New JSON value to write at the path."
3269
+ ).option("--in-place", "Write the updated content back to the input file.").action(
3270
+ (options) => {
3271
+ const source = readSingleJsonSource(options);
3272
+ const updated = setJsonPathValue(
3273
+ source.parsed,
3274
+ options.path,
3275
+ parseJsonInput(options.setValue)
3276
+ );
3277
+ const formatted = formatJsonValue(updated, 2, false);
3278
+ if (options.inPlace) {
3279
+ if (!source.sourcePath) {
3280
+ throw new Error("--in-place requires --file.");
3281
+ }
3282
+ writeTextFile(source.sourcePath, `${formatted}
3283
+ `);
3284
+ }
3285
+ printValue(updated);
3286
+ }
3287
+ );
3288
+ jsonCommand.command("delete").description("Remove a path from JSON.").option("--file <path>", "Read JSON from a file.").option("--value <json>", "Read JSON inline.").requiredOption("--path <value>", "JSON path to delete.").option("--in-place", "Write the updated content back to the input file.").action(
3289
+ (options) => {
3290
+ const source = readSingleJsonSource(options);
3291
+ const updated = deleteJsonPathValue(source.parsed, options.path);
3292
+ const formatted = formatJsonValue(updated, 2, false);
3293
+ if (options.inPlace) {
3294
+ if (!source.sourcePath) {
3295
+ throw new Error("--in-place requires --file.");
3296
+ }
3297
+ writeTextFile(source.sourcePath, `${formatted}
3298
+ `);
3299
+ }
3300
+ printValue(updated);
3301
+ }
3302
+ );
3303
+ jsonCommand.command("diff").description("Diff two JSON values or files.").requiredOption(
3304
+ "--left <source>",
3305
+ "Left JSON file path or inline JSON value."
3306
+ ).requiredOption(
3307
+ "--right <source>",
3308
+ "Right JSON file path or inline JSON value."
3309
+ ).action((options) => {
3310
+ printValue(
3311
+ diffJsonValues(
3312
+ readDiffJsonSource(options.left),
3313
+ readDiffJsonSource(options.right)
3314
+ )
3315
+ );
3316
+ });
3317
+ jsonCommand.command("merge").description("Merge multiple JSON sources.").option("--file <paths...>", "Read JSON from one or more files.").option("--value <json...>", "Read one or more inline JSON values.").action(
3318
+ (options) => {
3319
+ printValue(mergeJsonValues(readMultipleJsonSources(options)));
3320
+ }
3321
+ );
3322
+ jsonCommand.command("to-yaml").description("Convert JSON to YAML.").option("--file <path>", "Read JSON from a file.").option("--value <json>", "Read JSON inline.").action((options) => {
3323
+ const source = readSingleJsonSource(options);
3324
+ printValue(convertJsonToYaml(source.parsed));
3325
+ });
3326
+ }
3327
+
3328
+ // src/cli/create-program.ts
3329
+ var CLI_VERSION = "1.1.0";
3330
+ function buildProgram() {
3331
+ const program = new Command();
3332
+ configureProgramUi(program);
3333
+ program.name("brutils").description("Core Brazilian developer utilities CLI.").version(CLI_VERSION).showHelpAfterError("(use --help for detailed usage)").showSuggestionAfterError(true).addHelpText("after", rootFooter());
3334
+ registerBrazilianCommands(program);
3335
+ registerTextDataCommands(program);
3336
+ registerHelperCommands(program);
3337
+ registerRandomCommands(program);
3338
+ registerArchiveCommands(program);
3339
+ return program;
3340
+ }
3341
+
3342
+ // src/cli.ts
3343
+ buildProgram().parseAsync(process.argv).catch((error) => {
3344
+ handleCliError(error);
3345
+ });
3346
+ //# sourceMappingURL=cli.js.map