@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/LICENSE +21 -0
- package/README.md +146 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3346 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +442 -0
- package/dist/index.js +2286 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2286 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/errors/brutils.error.ts
|
|
4
|
+
var BRUtilsError = class extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "BRUtilsError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/core/utils/digits.ts
|
|
12
|
+
function onlyDigits(value) {
|
|
13
|
+
return value.replace(/\D/g, "");
|
|
14
|
+
}
|
|
15
|
+
function allDigitsEqual(value) {
|
|
16
|
+
return /^(\d)\1+$/.test(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/services/cpf/cpf.shared.ts
|
|
20
|
+
var CPF_STATE_REGION_MAP = {
|
|
21
|
+
AC: 2,
|
|
22
|
+
AL: 4,
|
|
23
|
+
AM: 2,
|
|
24
|
+
AP: 2,
|
|
25
|
+
BA: 5,
|
|
26
|
+
CE: 3,
|
|
27
|
+
DF: 1,
|
|
28
|
+
ES: 7,
|
|
29
|
+
GO: 1,
|
|
30
|
+
MA: 3,
|
|
31
|
+
MG: 6,
|
|
32
|
+
MS: 1,
|
|
33
|
+
MT: 1,
|
|
34
|
+
PA: 2,
|
|
35
|
+
PB: 4,
|
|
36
|
+
PE: 4,
|
|
37
|
+
PI: 3,
|
|
38
|
+
PR: 9,
|
|
39
|
+
RJ: 7,
|
|
40
|
+
RN: 4,
|
|
41
|
+
RO: 2,
|
|
42
|
+
RR: 2,
|
|
43
|
+
RS: 0,
|
|
44
|
+
SC: 9,
|
|
45
|
+
SE: 5,
|
|
46
|
+
SP: 8,
|
|
47
|
+
TO: 1
|
|
48
|
+
};
|
|
49
|
+
var REGION_TO_STATE = {
|
|
50
|
+
0: "RS",
|
|
51
|
+
1: "DF",
|
|
52
|
+
2: "AC",
|
|
53
|
+
3: "CE",
|
|
54
|
+
4: "AL",
|
|
55
|
+
5: "BA",
|
|
56
|
+
6: "MG",
|
|
57
|
+
7: "RJ",
|
|
58
|
+
8: "SP",
|
|
59
|
+
9: "PR"
|
|
60
|
+
};
|
|
61
|
+
function normalizeCPF(value) {
|
|
62
|
+
return onlyDigits(value);
|
|
63
|
+
}
|
|
64
|
+
function stripCPF(value) {
|
|
65
|
+
return normalizeCPF(value);
|
|
66
|
+
}
|
|
67
|
+
function ensureCPFLength(value) {
|
|
68
|
+
const digits = normalizeCPF(value);
|
|
69
|
+
if (digits.length !== 11)
|
|
70
|
+
throw new BRUtilsError("CPF must contain exactly 11 digits.");
|
|
71
|
+
return digits;
|
|
72
|
+
}
|
|
73
|
+
function calculateCPFCheckDigit(digits) {
|
|
74
|
+
const factorStart = digits.length + 1;
|
|
75
|
+
const sum = digits.reduce(
|
|
76
|
+
(acc, digit, index) => acc + digit * (factorStart - index),
|
|
77
|
+
0
|
|
78
|
+
);
|
|
79
|
+
const remainder = sum * 10 % 11;
|
|
80
|
+
return remainder === 10 ? 0 : remainder;
|
|
81
|
+
}
|
|
82
|
+
function resolveCPFRegionDigit(state) {
|
|
83
|
+
return state ? CPF_STATE_REGION_MAP[state] : void 0;
|
|
84
|
+
}
|
|
85
|
+
function resolveCPFStateFromDigits(value) {
|
|
86
|
+
const digits = normalizeCPF(value);
|
|
87
|
+
if (digits.length < 9) return void 0;
|
|
88
|
+
return REGION_TO_STATE[Number(digits[8])];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/core/utils/format.ts
|
|
92
|
+
function formatCPF(value) {
|
|
93
|
+
return value.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4");
|
|
94
|
+
}
|
|
95
|
+
function formatCNPJ(value) {
|
|
96
|
+
return value.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, "$1.$2.$3/$4-$5");
|
|
97
|
+
}
|
|
98
|
+
function formatCEP(value) {
|
|
99
|
+
return value.replace(/(\d{5})(\d{3})/, "$1-$2");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/services/cpf/cpf.formatter.ts
|
|
103
|
+
function formatCPFValue(value) {
|
|
104
|
+
return formatCPF(ensureCPFLength(value));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/core/utils/mask.ts
|
|
108
|
+
function applyDigitMask(value, pattern) {
|
|
109
|
+
let index = 0;
|
|
110
|
+
let result = "";
|
|
111
|
+
for (const token of pattern) {
|
|
112
|
+
if (token === "*") {
|
|
113
|
+
if (value[index] === void 0)
|
|
114
|
+
throw new BRUtilsError(
|
|
115
|
+
"Mask pattern consumes more digits than the input provides."
|
|
116
|
+
);
|
|
117
|
+
result += "*";
|
|
118
|
+
index += 1;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (token === "#" || /\d/.test(token)) {
|
|
122
|
+
const digit = value[index];
|
|
123
|
+
if (digit === void 0)
|
|
124
|
+
throw new BRUtilsError(
|
|
125
|
+
"Mask pattern consumes more digits than the input provides."
|
|
126
|
+
);
|
|
127
|
+
result += digit;
|
|
128
|
+
index += 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
result += token;
|
|
132
|
+
}
|
|
133
|
+
if (index != value.length)
|
|
134
|
+
throw new BRUtilsError(
|
|
135
|
+
`Mask pattern consumed ${index} digits, but the input has ${value.length}.`
|
|
136
|
+
);
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/services/cpf/cpf.masker.ts
|
|
141
|
+
var DEFAULT_CPF_MASK_PATTERN = "***.***.***-##";
|
|
142
|
+
function maskCPF(value, options = {}) {
|
|
143
|
+
return applyDigitMask(
|
|
144
|
+
ensureCPFLength(value),
|
|
145
|
+
options.pattern ?? DEFAULT_CPF_MASK_PATTERN
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/core/utils/random.ts
|
|
150
|
+
function randomDigit() {
|
|
151
|
+
return Math.floor(Math.random() * 10);
|
|
152
|
+
}
|
|
153
|
+
function randomDigits(length) {
|
|
154
|
+
return Array.from({ length }, () => randomDigit());
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/services/cpf/cpf.generator.ts
|
|
158
|
+
function generateCPF(options = {}) {
|
|
159
|
+
const firstEightDigits = randomDigits(8);
|
|
160
|
+
const regionDigit = resolveCPFRegionDigit(options.state) ?? randomDigit();
|
|
161
|
+
const baseDigits = [...firstEightDigits, regionDigit];
|
|
162
|
+
const firstCheckDigit = calculateCPFCheckDigit(baseDigits);
|
|
163
|
+
const secondCheckDigit = calculateCPFCheckDigit([
|
|
164
|
+
...baseDigits,
|
|
165
|
+
firstCheckDigit
|
|
166
|
+
]);
|
|
167
|
+
const cpf = [...baseDigits, firstCheckDigit, secondCheckDigit].join("");
|
|
168
|
+
return options.formatted ? formatCPF(cpf) : cpf;
|
|
169
|
+
}
|
|
170
|
+
function generateCPFBatch(options) {
|
|
171
|
+
const count = options.count;
|
|
172
|
+
if (!Number.isInteger(count) || count <= 0)
|
|
173
|
+
throw new BRUtilsError("Count must be a positive integer.");
|
|
174
|
+
if (!options.unique)
|
|
175
|
+
return Array.from(
|
|
176
|
+
{ length: count },
|
|
177
|
+
() => generateCPF({ formatted: options.formatted, state: options.state })
|
|
178
|
+
);
|
|
179
|
+
const seen = /* @__PURE__ */ new Set();
|
|
180
|
+
const values = [];
|
|
181
|
+
while (values.length < count) {
|
|
182
|
+
const value = generateCPF({
|
|
183
|
+
formatted: options.formatted,
|
|
184
|
+
state: options.state
|
|
185
|
+
});
|
|
186
|
+
if (!seen.has(value)) {
|
|
187
|
+
seen.add(value);
|
|
188
|
+
values.push(value);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return values;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/services/cpf/cpf.validator.ts
|
|
195
|
+
function validateCPF(value, options = {}) {
|
|
196
|
+
const digits = normalizeCPF(value);
|
|
197
|
+
if (digits.length !== 11) return { isValid: false, value: digits };
|
|
198
|
+
if (options.strict && allDigitsEqual(digits))
|
|
199
|
+
return { isValid: false, value: digits, formatted: formatCPF(digits) };
|
|
200
|
+
const baseDigits = digits.slice(0, 9).split("").map(Number);
|
|
201
|
+
const firstCheckDigit = calculateCPFCheckDigit(baseDigits);
|
|
202
|
+
const secondCheckDigit = calculateCPFCheckDigit([
|
|
203
|
+
...baseDigits,
|
|
204
|
+
firstCheckDigit
|
|
205
|
+
]);
|
|
206
|
+
const expectedCPF = [...baseDigits, firstCheckDigit, secondCheckDigit].join(
|
|
207
|
+
""
|
|
208
|
+
);
|
|
209
|
+
return {
|
|
210
|
+
isValid: digits === expectedCPF,
|
|
211
|
+
value: digits,
|
|
212
|
+
formatted: formatCPF(digits),
|
|
213
|
+
state: resolveCPFStateFromDigits(digits)
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/services/cnpj/cnpj.shared.ts
|
|
218
|
+
function normalizeCNPJ(value) {
|
|
219
|
+
return onlyDigits(value);
|
|
220
|
+
}
|
|
221
|
+
function stripCNPJ(value) {
|
|
222
|
+
return normalizeCNPJ(value);
|
|
223
|
+
}
|
|
224
|
+
function ensureCNPJLength(value) {
|
|
225
|
+
const digits = normalizeCNPJ(value);
|
|
226
|
+
if (digits.length !== 14)
|
|
227
|
+
throw new BRUtilsError("CNPJ must contain exactly 14 digits.");
|
|
228
|
+
return digits;
|
|
229
|
+
}
|
|
230
|
+
function normalizeCNPJBranch(value) {
|
|
231
|
+
const branch = normalizeCNPJ(value ?? "0001");
|
|
232
|
+
if (!/^\d{1,4}$/.test(branch))
|
|
233
|
+
throw new BRUtilsError("Branch must contain between 1 and 4 digits.");
|
|
234
|
+
return branch.padStart(4, "0");
|
|
235
|
+
}
|
|
236
|
+
function calculateCNPJCheckDigit(digits) {
|
|
237
|
+
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];
|
|
238
|
+
const sum = digits.reduce(
|
|
239
|
+
(acc, digit, index) => acc + digit * weights[index],
|
|
240
|
+
0
|
|
241
|
+
);
|
|
242
|
+
const remainder = sum % 11;
|
|
243
|
+
return remainder < 2 ? 0 : 11 - remainder;
|
|
244
|
+
}
|
|
245
|
+
function resolveCNPJBranch(value) {
|
|
246
|
+
const digits = normalizeCNPJ(value);
|
|
247
|
+
return digits.length < 12 ? void 0 : digits.slice(8, 12);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/services/cnpj/cnpj.formatter.ts
|
|
251
|
+
function formatCNPJValue(value) {
|
|
252
|
+
return formatCNPJ(ensureCNPJLength(value));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/services/cnpj/cnpj.masker.ts
|
|
256
|
+
var DEFAULT_CNPJ_MASK_PATTERN = "**.***.***/****-##";
|
|
257
|
+
function maskCNPJ(value, options = {}) {
|
|
258
|
+
return applyDigitMask(
|
|
259
|
+
ensureCNPJLength(value),
|
|
260
|
+
options.pattern ?? DEFAULT_CNPJ_MASK_PATTERN
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/services/cnpj/cnpj.generator.ts
|
|
265
|
+
function generateCNPJ(options = {}) {
|
|
266
|
+
const rootDigits = randomDigits(8);
|
|
267
|
+
const branchDigits = normalizeCNPJBranch(options.branch).split("").map(Number);
|
|
268
|
+
const baseDigits = [...rootDigits, ...branchDigits];
|
|
269
|
+
const firstCheckDigit = calculateCNPJCheckDigit(baseDigits);
|
|
270
|
+
const secondCheckDigit = calculateCNPJCheckDigit([
|
|
271
|
+
...baseDigits,
|
|
272
|
+
firstCheckDigit
|
|
273
|
+
]);
|
|
274
|
+
const cnpj = [...baseDigits, firstCheckDigit, secondCheckDigit].join("");
|
|
275
|
+
return options.formatted ? formatCNPJ(cnpj) : cnpj;
|
|
276
|
+
}
|
|
277
|
+
function generateCNPJBatch(options) {
|
|
278
|
+
const count = options.count;
|
|
279
|
+
if (!Number.isInteger(count) || count <= 0)
|
|
280
|
+
throw new BRUtilsError("Count must be a positive integer.");
|
|
281
|
+
if (!options.unique)
|
|
282
|
+
return Array.from(
|
|
283
|
+
{ length: count },
|
|
284
|
+
() => generateCNPJ({ formatted: options.formatted, branch: options.branch })
|
|
285
|
+
);
|
|
286
|
+
const seen = /* @__PURE__ */ new Set();
|
|
287
|
+
const values = [];
|
|
288
|
+
while (values.length < count) {
|
|
289
|
+
const value = generateCNPJ({
|
|
290
|
+
formatted: options.formatted,
|
|
291
|
+
branch: options.branch
|
|
292
|
+
});
|
|
293
|
+
if (!seen.has(value)) {
|
|
294
|
+
seen.add(value);
|
|
295
|
+
values.push(value);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return values;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/services/cnpj/cnpj.validator.ts
|
|
302
|
+
function validateCNPJ(value, options = {}) {
|
|
303
|
+
const digits = normalizeCNPJ(value);
|
|
304
|
+
if (digits.length !== 14) return { isValid: false, value: digits };
|
|
305
|
+
if (options.strict && allDigitsEqual(digits))
|
|
306
|
+
return {
|
|
307
|
+
isValid: false,
|
|
308
|
+
value: digits,
|
|
309
|
+
formatted: formatCNPJ(digits),
|
|
310
|
+
branch: resolveCNPJBranch(digits)
|
|
311
|
+
};
|
|
312
|
+
const baseDigits = digits.slice(0, 12).split("").map(Number);
|
|
313
|
+
const firstCheckDigit = calculateCNPJCheckDigit(baseDigits);
|
|
314
|
+
const secondCheckDigit = calculateCNPJCheckDigit([
|
|
315
|
+
...baseDigits,
|
|
316
|
+
firstCheckDigit
|
|
317
|
+
]);
|
|
318
|
+
const expectedCNPJ = [...baseDigits, firstCheckDigit, secondCheckDigit].join(
|
|
319
|
+
""
|
|
320
|
+
);
|
|
321
|
+
return {
|
|
322
|
+
isValid: digits === expectedCNPJ,
|
|
323
|
+
value: digits,
|
|
324
|
+
formatted: formatCNPJ(digits),
|
|
325
|
+
branch: resolveCNPJBranch(digits)
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/services/cep/cep.shared.ts
|
|
330
|
+
var CEP_STATE_LEADING_DIGIT_MAP = {
|
|
331
|
+
AC: [6],
|
|
332
|
+
AL: [5],
|
|
333
|
+
AM: [6],
|
|
334
|
+
AP: [6],
|
|
335
|
+
BA: [4],
|
|
336
|
+
CE: [6],
|
|
337
|
+
DF: [7],
|
|
338
|
+
ES: [2],
|
|
339
|
+
GO: [7],
|
|
340
|
+
MA: [6],
|
|
341
|
+
MG: [3],
|
|
342
|
+
MS: [7],
|
|
343
|
+
MT: [7],
|
|
344
|
+
PA: [6],
|
|
345
|
+
PB: [5],
|
|
346
|
+
PE: [5],
|
|
347
|
+
PI: [6],
|
|
348
|
+
PR: [8],
|
|
349
|
+
RJ: [2],
|
|
350
|
+
RN: [5],
|
|
351
|
+
RO: [7],
|
|
352
|
+
RR: [6],
|
|
353
|
+
RS: [9],
|
|
354
|
+
SC: [8],
|
|
355
|
+
SE: [4],
|
|
356
|
+
SP: [0, 1],
|
|
357
|
+
TO: [7]
|
|
358
|
+
};
|
|
359
|
+
function normalizeCEP(value) {
|
|
360
|
+
return onlyDigits(value);
|
|
361
|
+
}
|
|
362
|
+
function stripCEP(value) {
|
|
363
|
+
return normalizeCEP(value);
|
|
364
|
+
}
|
|
365
|
+
function ensureCEPLength(value) {
|
|
366
|
+
const digits = normalizeCEP(value);
|
|
367
|
+
if (digits.length !== 8)
|
|
368
|
+
throw new BRUtilsError("CEP must contain exactly 8 digits.");
|
|
369
|
+
return digits;
|
|
370
|
+
}
|
|
371
|
+
function resolveCEPLeadingDigits(state) {
|
|
372
|
+
return state ? CEP_STATE_LEADING_DIGIT_MAP[state] : void 0;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/services/cep/cep.formatter.ts
|
|
376
|
+
function formatCEPValue(value) {
|
|
377
|
+
return formatCEP(ensureCEPLength(value));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/services/cep/cep.masker.ts
|
|
381
|
+
var DEFAULT_CEP_MASK_PATTERN = "*****-###";
|
|
382
|
+
function maskCEP(value, options = {}) {
|
|
383
|
+
return applyDigitMask(
|
|
384
|
+
ensureCEPLength(value),
|
|
385
|
+
options.pattern ?? DEFAULT_CEP_MASK_PATTERN
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/services/cep/cep.generator.ts
|
|
390
|
+
function generateCEP(options = {}) {
|
|
391
|
+
const stateLeadingDigits = resolveCEPLeadingDigits(options.state);
|
|
392
|
+
const firstDigit = stateLeadingDigits ? stateLeadingDigits[Math.floor(Math.random() * stateLeadingDigits.length)] : randomDigit();
|
|
393
|
+
const digits = [firstDigit, ...randomDigits(7)].join("");
|
|
394
|
+
return options.formatted ? formatCEP(digits) : digits;
|
|
395
|
+
}
|
|
396
|
+
function generateCEPBatch(options) {
|
|
397
|
+
const count = options.count;
|
|
398
|
+
if (!Number.isInteger(count) || count <= 0)
|
|
399
|
+
throw new BRUtilsError("Count must be a positive integer.");
|
|
400
|
+
return Array.from(
|
|
401
|
+
{ length: count },
|
|
402
|
+
() => generateCEP({ formatted: options.formatted, state: options.state })
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/services/cep/cep.validator.ts
|
|
407
|
+
function validateCEP(value, options = {}) {
|
|
408
|
+
const digits = normalizeCEP(value);
|
|
409
|
+
const hasValidLength = digits.length === 8;
|
|
410
|
+
const passesStrictRules = !options.strict || !allDigitsEqual(digits);
|
|
411
|
+
return {
|
|
412
|
+
isValid: hasValidLength && passesStrictRules,
|
|
413
|
+
value: digits,
|
|
414
|
+
formatted: hasValidLength ? formatCEP(digits) : void 0
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/services/credit-card/credit-card.constants.ts
|
|
419
|
+
var CREDIT_CARD_BRAND_PREFIXES = {
|
|
420
|
+
visa: ["4"],
|
|
421
|
+
mastercard: ["51", "52", "53", "54", "55"],
|
|
422
|
+
amex: ["34", "37"],
|
|
423
|
+
elo: [
|
|
424
|
+
"401178",
|
|
425
|
+
"401179",
|
|
426
|
+
"431274",
|
|
427
|
+
"438935",
|
|
428
|
+
"451416",
|
|
429
|
+
"457393",
|
|
430
|
+
"457631",
|
|
431
|
+
"457632",
|
|
432
|
+
"504175",
|
|
433
|
+
"627780",
|
|
434
|
+
"636297",
|
|
435
|
+
"636368"
|
|
436
|
+
]
|
|
437
|
+
};
|
|
438
|
+
var CREDIT_CARD_BRAND_LENGTHS = {
|
|
439
|
+
visa: [16],
|
|
440
|
+
mastercard: [16],
|
|
441
|
+
amex: [15],
|
|
442
|
+
elo: [16]
|
|
443
|
+
};
|
|
444
|
+
var CREDIT_CARD_BRAND_CVV_LENGTH = {
|
|
445
|
+
visa: 3,
|
|
446
|
+
mastercard: 3,
|
|
447
|
+
amex: 4,
|
|
448
|
+
elo: 3
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// src/services/credit-card/credit-card.detector.ts
|
|
452
|
+
function detectCreditCardBrand(number) {
|
|
453
|
+
const digits = onlyDigits(number);
|
|
454
|
+
const brands = Object.keys(CREDIT_CARD_BRAND_PREFIXES);
|
|
455
|
+
for (const brand of brands) {
|
|
456
|
+
const prefixes = CREDIT_CARD_BRAND_PREFIXES[brand];
|
|
457
|
+
const lengths = CREDIT_CARD_BRAND_LENGTHS[brand];
|
|
458
|
+
const matchesPrefix = prefixes.some((prefix) => digits.startsWith(prefix));
|
|
459
|
+
const matchesLength = lengths.includes(digits.length);
|
|
460
|
+
if (matchesPrefix && matchesLength) {
|
|
461
|
+
return brand;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return "unknown";
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/core/utils/array.ts
|
|
468
|
+
function pickRandomItem(items, randomSource = Math.random) {
|
|
469
|
+
const item = items[Math.floor(randomSource() * items.length)];
|
|
470
|
+
if (item === void 0) {
|
|
471
|
+
throw new BRUtilsError("Cannot pick a random item from an empty array.");
|
|
472
|
+
}
|
|
473
|
+
return item;
|
|
474
|
+
}
|
|
475
|
+
function getItemAtIndex(items, index) {
|
|
476
|
+
const item = items[index];
|
|
477
|
+
if (item === void 0) {
|
|
478
|
+
throw new BRUtilsError(`Array index out of bounds: ${index}`);
|
|
479
|
+
}
|
|
480
|
+
return item;
|
|
481
|
+
}
|
|
482
|
+
function shuffleArray(items, randomSource = Math.random) {
|
|
483
|
+
const result = [...items];
|
|
484
|
+
for (let index = result.length - 1; index > 0; index -= 1) {
|
|
485
|
+
const swapIndex = Math.floor(randomSource() * (index + 1));
|
|
486
|
+
const currentValue = getItemAtIndex(result, index);
|
|
487
|
+
const swapValue = getItemAtIndex(result, swapIndex);
|
|
488
|
+
result[index] = swapValue;
|
|
489
|
+
result[swapIndex] = currentValue;
|
|
490
|
+
}
|
|
491
|
+
return result;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/services/credit-card/credit-card.generator.ts
|
|
495
|
+
function generateRandomNumericString(length) {
|
|
496
|
+
return Array.from({ length }, () => String(randomDigit())).join("");
|
|
497
|
+
}
|
|
498
|
+
function formatCreditCardNumber(value) {
|
|
499
|
+
return onlyDigits(value).replace(/(\d{4})(?=\d)/g, "$1 ").trim();
|
|
500
|
+
}
|
|
501
|
+
function calculateLuhnCheckDigit(partialNumber) {
|
|
502
|
+
const digits = partialNumber.split("").map(Number).reverse();
|
|
503
|
+
const sum = digits.reduce((accumulator, digit, index) => {
|
|
504
|
+
if (index % 2 === 0) {
|
|
505
|
+
const doubled = digit * 2;
|
|
506
|
+
return accumulator + (doubled > 9 ? doubled - 9 : doubled);
|
|
507
|
+
}
|
|
508
|
+
return accumulator + digit;
|
|
509
|
+
}, 0);
|
|
510
|
+
return (10 - sum % 10) % 10;
|
|
511
|
+
}
|
|
512
|
+
function generateCardNumber(brand) {
|
|
513
|
+
const prefixes = CREDIT_CARD_BRAND_PREFIXES[brand];
|
|
514
|
+
const lengths = CREDIT_CARD_BRAND_LENGTHS[brand];
|
|
515
|
+
const prefix = pickRandomItem(prefixes);
|
|
516
|
+
const totalLength = pickRandomItem(lengths);
|
|
517
|
+
const bodyLength = totalLength - prefix.length - 1;
|
|
518
|
+
const partialNumber = prefix + generateRandomNumericString(bodyLength);
|
|
519
|
+
const checkDigit = calculateLuhnCheckDigit(partialNumber);
|
|
520
|
+
return partialNumber + String(checkDigit);
|
|
521
|
+
}
|
|
522
|
+
function generateExpiry(expiryYearsAhead = 5) {
|
|
523
|
+
const now = /* @__PURE__ */ new Date();
|
|
524
|
+
const month = String(Math.floor(Math.random() * 12) + 1).padStart(2, "0");
|
|
525
|
+
const futureYear = now.getFullYear() + Math.floor(Math.random() * Math.max(expiryYearsAhead, 1)) + 1;
|
|
526
|
+
const yearTwoDigits = String(futureYear).slice(-2);
|
|
527
|
+
return {
|
|
528
|
+
expiryMonth: month,
|
|
529
|
+
expiryYear: yearTwoDigits,
|
|
530
|
+
expiry: `${month}/${yearTwoDigits}`
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function generateCVV(brand) {
|
|
534
|
+
const length = CREDIT_CARD_BRAND_CVV_LENGTH[brand];
|
|
535
|
+
return generateRandomNumericString(length);
|
|
536
|
+
}
|
|
537
|
+
function generateCreditCard(options = {}) {
|
|
538
|
+
const brand = options.brand ?? "visa";
|
|
539
|
+
const rawNumber = generateCardNumber(brand);
|
|
540
|
+
const number = options.formatted ? formatCreditCardNumber(rawNumber) : rawNumber;
|
|
541
|
+
const { expiryMonth, expiryYear, expiry } = generateExpiry(
|
|
542
|
+
options.expiryYearsAhead
|
|
543
|
+
);
|
|
544
|
+
const cvv = generateCVV(brand);
|
|
545
|
+
return {
|
|
546
|
+
brand,
|
|
547
|
+
number,
|
|
548
|
+
expiryMonth,
|
|
549
|
+
expiryYear,
|
|
550
|
+
expiry,
|
|
551
|
+
cvv
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/services/credit-card/credit-card.validator.ts
|
|
556
|
+
function isValidLuhn(number) {
|
|
557
|
+
const digits = onlyDigits(number);
|
|
558
|
+
let sum = 0;
|
|
559
|
+
let shouldDouble = false;
|
|
560
|
+
for (let index = digits.length - 1; index >= 0; index -= 1) {
|
|
561
|
+
let digit = Number(digits[index]);
|
|
562
|
+
if (shouldDouble) {
|
|
563
|
+
digit *= 2;
|
|
564
|
+
if (digit > 9) {
|
|
565
|
+
digit -= 9;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
sum += digit;
|
|
569
|
+
shouldDouble = !shouldDouble;
|
|
570
|
+
}
|
|
571
|
+
return sum % 10 === 0;
|
|
572
|
+
}
|
|
573
|
+
function isValidExpiry(expiryMonth, expiryYear, expiry) {
|
|
574
|
+
let month = expiryMonth;
|
|
575
|
+
let year = expiryYear;
|
|
576
|
+
if ((!month || !year) && expiry) {
|
|
577
|
+
const match = expiry.match(/^(\d{2})\/(\d{2})$/);
|
|
578
|
+
if (!match) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
month = match[1];
|
|
582
|
+
year = match[2];
|
|
583
|
+
}
|
|
584
|
+
if (!month || !year) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
const monthNumber = Number(month);
|
|
588
|
+
const yearNumber = Number(year);
|
|
589
|
+
if (monthNumber < 1 || monthNumber > 12) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
const now = /* @__PURE__ */ new Date();
|
|
593
|
+
const currentYear = Number(String(now.getFullYear()).slice(-2));
|
|
594
|
+
const currentMonth = now.getMonth() + 1;
|
|
595
|
+
if (yearNumber > currentYear) {
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
if (yearNumber === currentYear && monthNumber >= currentMonth) {
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
function isValidCVV(brand, cvv) {
|
|
604
|
+
if (!cvv) {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
const digits = onlyDigits(cvv);
|
|
608
|
+
if (brand === "unknown") {
|
|
609
|
+
return digits.length >= 3 && digits.length <= 4;
|
|
610
|
+
}
|
|
611
|
+
const expectedLength = CREDIT_CARD_BRAND_CVV_LENGTH[brand];
|
|
612
|
+
return digits.length === expectedLength;
|
|
613
|
+
}
|
|
614
|
+
function validateCreditCard(input) {
|
|
615
|
+
const number = onlyDigits(input.number);
|
|
616
|
+
const brand = detectCreditCardBrand(number);
|
|
617
|
+
const numberValid = brand !== "unknown" && CREDIT_CARD_BRAND_LENGTHS[brand].includes(number.length) && isValidLuhn(number);
|
|
618
|
+
const expiryValid = isValidExpiry(
|
|
619
|
+
input.expiryMonth,
|
|
620
|
+
input.expiryYear,
|
|
621
|
+
input.expiry
|
|
622
|
+
);
|
|
623
|
+
const cvvValid = isValidCVV(brand, input.cvv);
|
|
624
|
+
return {
|
|
625
|
+
isValid: numberValid && expiryValid && cvvValid,
|
|
626
|
+
brand,
|
|
627
|
+
number,
|
|
628
|
+
numberValid,
|
|
629
|
+
expiryValid,
|
|
630
|
+
cvvValid
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/core/utils/seeded-random.ts
|
|
635
|
+
function createRandomSource(seed) {
|
|
636
|
+
if (seed === void 0) {
|
|
637
|
+
return Math.random;
|
|
638
|
+
}
|
|
639
|
+
let state = Math.trunc(seed) >>> 0;
|
|
640
|
+
return () => {
|
|
641
|
+
state = state + 1831565813 >>> 0;
|
|
642
|
+
let value = state;
|
|
643
|
+
value = Math.imul(value ^ value >>> 15, value | 1);
|
|
644
|
+
value ^= value + Math.imul(value ^ value >>> 7, value | 61);
|
|
645
|
+
return ((value ^ value >>> 14) >>> 0) / 4294967296;
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// src/services/random-number/random-number.generator.ts
|
|
650
|
+
function resolveIntegerMin(min) {
|
|
651
|
+
return min ?? Number.MIN_SAFE_INTEGER;
|
|
652
|
+
}
|
|
653
|
+
function resolveIntegerMax(max) {
|
|
654
|
+
return max ?? Number.MAX_SAFE_INTEGER;
|
|
655
|
+
}
|
|
656
|
+
function resolveFloatMin(min) {
|
|
657
|
+
return min ?? 0;
|
|
658
|
+
}
|
|
659
|
+
function resolveFloatMax(max) {
|
|
660
|
+
return max ?? 1;
|
|
661
|
+
}
|
|
662
|
+
function randomIntegerBetween(min, max, randomSource) {
|
|
663
|
+
return Math.floor(randomSource() * (max - min + 1)) + min;
|
|
664
|
+
}
|
|
665
|
+
function randomFloatBetween(min, max, randomSource) {
|
|
666
|
+
return randomSource() * (max - min) + min;
|
|
667
|
+
}
|
|
668
|
+
function validateRange(min, max, integerOnly = true) {
|
|
669
|
+
if (integerOnly && (!Number.isInteger(min) || !Number.isInteger(max))) {
|
|
670
|
+
throw new BRUtilsError("Minimum and maximum values must be integers.");
|
|
671
|
+
}
|
|
672
|
+
if (min > max) {
|
|
673
|
+
throw new BRUtilsError(
|
|
674
|
+
"Minimum value cannot be greater than maximum value."
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function validateCount(count) {
|
|
679
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
680
|
+
throw new BRUtilsError("Count must be a positive integer.");
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function normalizePrecision(precision) {
|
|
684
|
+
if (precision === void 0) {
|
|
685
|
+
return void 0;
|
|
686
|
+
}
|
|
687
|
+
if (!Number.isInteger(precision) || precision < 0) {
|
|
688
|
+
throw new BRUtilsError("Precision must be a non-negative integer.");
|
|
689
|
+
}
|
|
690
|
+
return precision;
|
|
691
|
+
}
|
|
692
|
+
function normalizeItems(items) {
|
|
693
|
+
const normalized = items.map((item) => item.trim()).filter(Boolean);
|
|
694
|
+
if (normalized.length === 0) {
|
|
695
|
+
throw new BRUtilsError("At least one item must be provided.");
|
|
696
|
+
}
|
|
697
|
+
return normalized;
|
|
698
|
+
}
|
|
699
|
+
function applyFloatPrecision(value, precision) {
|
|
700
|
+
if (precision === void 0) {
|
|
701
|
+
return value;
|
|
702
|
+
}
|
|
703
|
+
return Number(value.toFixed(precision));
|
|
704
|
+
}
|
|
705
|
+
function generateRandomIntegers(options = {}) {
|
|
706
|
+
const min = resolveIntegerMin(options.min);
|
|
707
|
+
const max = resolveIntegerMax(options.max);
|
|
708
|
+
const count = options.count ?? 1;
|
|
709
|
+
const unique = options.unique ?? false;
|
|
710
|
+
const sorted = options.sorted ?? false;
|
|
711
|
+
const randomSource = createRandomSource(options.seed);
|
|
712
|
+
validateRange(min, max, true);
|
|
713
|
+
validateCount(count);
|
|
714
|
+
const availableNumbers = max - min + 1;
|
|
715
|
+
if (unique && count > availableNumbers) {
|
|
716
|
+
throw new BRUtilsError(
|
|
717
|
+
"Cannot generate more unique numbers than the available range size."
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
let result;
|
|
721
|
+
if (unique) {
|
|
722
|
+
const pool = Array.from(
|
|
723
|
+
{ length: availableNumbers },
|
|
724
|
+
(_, index) => min + index
|
|
725
|
+
);
|
|
726
|
+
result = shuffleArray(pool, randomSource).slice(0, count);
|
|
727
|
+
} else {
|
|
728
|
+
result = Array.from(
|
|
729
|
+
{ length: count },
|
|
730
|
+
() => randomIntegerBetween(min, max, randomSource)
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
if (sorted) {
|
|
734
|
+
result.sort((a, b) => a - b);
|
|
735
|
+
}
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
function generateRandomNumbers(options = {}) {
|
|
739
|
+
return generateRandomIntegers(options);
|
|
740
|
+
}
|
|
741
|
+
function generateRandomFloats(options = {}) {
|
|
742
|
+
const min = resolveFloatMin(options.min);
|
|
743
|
+
const max = resolveFloatMax(options.max);
|
|
744
|
+
const count = options.count ?? 1;
|
|
745
|
+
const sorted = options.sorted ?? false;
|
|
746
|
+
const precision = normalizePrecision(options.precision);
|
|
747
|
+
const randomSource = createRandomSource(options.seed);
|
|
748
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
749
|
+
throw new BRUtilsError(
|
|
750
|
+
"Minimum and maximum values must be finite numbers."
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
validateRange(min, max, false);
|
|
754
|
+
validateCount(count);
|
|
755
|
+
const result = Array.from(
|
|
756
|
+
{ length: count },
|
|
757
|
+
() => applyFloatPrecision(randomFloatBetween(min, max, randomSource), precision)
|
|
758
|
+
);
|
|
759
|
+
if (sorted) {
|
|
760
|
+
result.sort((a, b) => a - b);
|
|
761
|
+
}
|
|
762
|
+
return result;
|
|
763
|
+
}
|
|
764
|
+
function pickRandomItems(options) {
|
|
765
|
+
const items = normalizeItems(options.items);
|
|
766
|
+
const count = options.count ?? 1;
|
|
767
|
+
const unique = options.unique ?? false;
|
|
768
|
+
const randomSource = createRandomSource(options.seed);
|
|
769
|
+
validateCount(count);
|
|
770
|
+
if (unique && count > items.length) {
|
|
771
|
+
throw new BRUtilsError(
|
|
772
|
+
"Cannot pick more unique items than the available item count."
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
if (unique) {
|
|
776
|
+
return shuffleArray(items, randomSource).slice(0, count);
|
|
777
|
+
}
|
|
778
|
+
return Array.from(
|
|
779
|
+
{ length: count },
|
|
780
|
+
() => pickRandomItem(items, randomSource)
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
function shuffleRandomItems(options) {
|
|
784
|
+
return shuffleArray(
|
|
785
|
+
normalizeItems(options.items),
|
|
786
|
+
createRandomSource(options.seed)
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
function rollDice(options = {}) {
|
|
790
|
+
const faces = options.faces ?? 6;
|
|
791
|
+
const count = options.count ?? 1;
|
|
792
|
+
const randomSource = createRandomSource(options.seed);
|
|
793
|
+
if (!Number.isInteger(faces) || faces < 2) {
|
|
794
|
+
throw new BRUtilsError(
|
|
795
|
+
"Dice faces must be an integer greater than or equal to 2."
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
validateCount(count);
|
|
799
|
+
return Array.from(
|
|
800
|
+
{ length: count },
|
|
801
|
+
() => randomIntegerBetween(1, faces, randomSource)
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
function flipCoin(options = {}) {
|
|
805
|
+
const randomSource = createRandomSource(options.seed);
|
|
806
|
+
return randomSource() < 0.5 ? "heads" : "tails";
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/services/number-picker/number-picker.service.ts
|
|
810
|
+
function resolveMin(min) {
|
|
811
|
+
return min ?? Number.MIN_SAFE_INTEGER;
|
|
812
|
+
}
|
|
813
|
+
function resolveMax(max) {
|
|
814
|
+
return max ?? Number.MAX_SAFE_INTEGER;
|
|
815
|
+
}
|
|
816
|
+
function pickRandomNumber(options = {}) {
|
|
817
|
+
const min = resolveMin(options.min);
|
|
818
|
+
const max = resolveMax(options.max);
|
|
819
|
+
const randomSource = createRandomSource(options.seed);
|
|
820
|
+
if (!Number.isInteger(min) || !Number.isInteger(max)) {
|
|
821
|
+
throw new BRUtilsError("Minimum and maximum values must be integers.");
|
|
822
|
+
}
|
|
823
|
+
if (min > max) {
|
|
824
|
+
throw new BRUtilsError(
|
|
825
|
+
"Minimum value cannot be greater than maximum value."
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
return Math.floor(randomSource() * (max - min + 1)) + min;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/services/zip/resolve-zip-output.ts
|
|
832
|
+
import path from "path";
|
|
833
|
+
function resolveZipOutputPath(sourcePath, explicitOut) {
|
|
834
|
+
if (explicitOut) {
|
|
835
|
+
return explicitOut;
|
|
836
|
+
}
|
|
837
|
+
const normalizedSource = path.resolve(sourcePath);
|
|
838
|
+
const parsed = path.parse(normalizedSource);
|
|
839
|
+
if (path.basename(normalizedSource) === ".") {
|
|
840
|
+
const cwd = process.cwd();
|
|
841
|
+
return path.join(cwd, `${path.basename(cwd)}.zip`);
|
|
842
|
+
}
|
|
843
|
+
if (parsed.ext) {
|
|
844
|
+
return path.join(parsed.dir, `${parsed.name}.zip`);
|
|
845
|
+
}
|
|
846
|
+
return path.join(parsed.dir, `${parsed.base}.zip`);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/services/zip/collect-zip-inputs.ts
|
|
850
|
+
import fs from "fs";
|
|
851
|
+
import path2 from "path";
|
|
852
|
+
import fg from "fast-glob";
|
|
853
|
+
function normalizeEntryName(value) {
|
|
854
|
+
return value.replace(/\\/g, "/");
|
|
855
|
+
}
|
|
856
|
+
function isDirectory(sourcePath) {
|
|
857
|
+
return fs.statSync(sourcePath).isDirectory();
|
|
858
|
+
}
|
|
859
|
+
function isFile(sourcePath) {
|
|
860
|
+
return fs.statSync(sourcePath).isFile();
|
|
861
|
+
}
|
|
862
|
+
function collectZipInputs(sourcePath, outputPath, options = {}) {
|
|
863
|
+
const resolvedSource = path2.resolve(sourcePath);
|
|
864
|
+
if (!fs.existsSync(resolvedSource)) {
|
|
865
|
+
throw new BRUtilsError(`Source path does not exist: ${sourcePath}`);
|
|
866
|
+
}
|
|
867
|
+
if (isFile(resolvedSource)) {
|
|
868
|
+
return [
|
|
869
|
+
{
|
|
870
|
+
type: "file",
|
|
871
|
+
sourcePath: resolvedSource,
|
|
872
|
+
entryName: path2.basename(resolvedSource)
|
|
873
|
+
}
|
|
874
|
+
];
|
|
875
|
+
}
|
|
876
|
+
if (!isDirectory(resolvedSource)) {
|
|
877
|
+
throw new BRUtilsError(`Unsupported source type: ${sourcePath}`);
|
|
878
|
+
}
|
|
879
|
+
const excludePatterns = options.exclude ?? [];
|
|
880
|
+
const outputAbsolutePath = path2.resolve(outputPath);
|
|
881
|
+
const rootEntryName = path2.basename(resolvedSource);
|
|
882
|
+
const entries = fg.sync(["**/*", "**/.*"], {
|
|
883
|
+
cwd: resolvedSource,
|
|
884
|
+
onlyFiles: false,
|
|
885
|
+
onlyDirectories: false,
|
|
886
|
+
dot: true,
|
|
887
|
+
followSymbolicLinks: options.followSymlinks ?? false,
|
|
888
|
+
unique: true,
|
|
889
|
+
ignore: excludePatterns,
|
|
890
|
+
markDirectories: true
|
|
891
|
+
});
|
|
892
|
+
if (entries.length === 0) {
|
|
893
|
+
return options.contentsOnly ? [] : [
|
|
894
|
+
{
|
|
895
|
+
type: "directory",
|
|
896
|
+
sourcePath: resolvedSource,
|
|
897
|
+
entryName: `${rootEntryName}/`
|
|
898
|
+
}
|
|
899
|
+
];
|
|
900
|
+
}
|
|
901
|
+
return entries.map((entry) => {
|
|
902
|
+
const cleanedEntry = entry.endsWith("/") ? entry.slice(0, -1) : entry;
|
|
903
|
+
const absoluteEntryPath = path2.join(resolvedSource, cleanedEntry);
|
|
904
|
+
if (path2.resolve(absoluteEntryPath) === outputAbsolutePath) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
const relativeEntryName = options.contentsOnly ? cleanedEntry : path2.join(rootEntryName, cleanedEntry);
|
|
908
|
+
const entryType = entry.endsWith("/") ? "directory" : "file";
|
|
909
|
+
return {
|
|
910
|
+
type: entryType,
|
|
911
|
+
sourcePath: absoluteEntryPath,
|
|
912
|
+
entryName: normalizeEntryName(
|
|
913
|
+
entryType === "directory" ? `${relativeEntryName}/` : relativeEntryName
|
|
914
|
+
)
|
|
915
|
+
};
|
|
916
|
+
}).filter((entry) => entry !== null);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/services/zip/create-zip.ts
|
|
920
|
+
import fs2 from "fs";
|
|
921
|
+
import path3 from "path";
|
|
922
|
+
import archiver from "archiver";
|
|
923
|
+
function resolveCompressionLevel(level) {
|
|
924
|
+
const resolvedLevel = level ?? 9;
|
|
925
|
+
if (!Number.isInteger(resolvedLevel) || resolvedLevel < 0 || resolvedLevel > 9) {
|
|
926
|
+
throw new BRUtilsError(
|
|
927
|
+
"Compression level must be an integer between 0 and 9."
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
return resolvedLevel;
|
|
931
|
+
}
|
|
932
|
+
function getSourceType(sourcePath) {
|
|
933
|
+
return fs2.statSync(sourcePath).isDirectory() ? "directory" : "file";
|
|
934
|
+
}
|
|
935
|
+
function ensureOutputCanBeWritten(outputPath, force = false) {
|
|
936
|
+
if (fs2.existsSync(outputPath) && !force) {
|
|
937
|
+
throw new BRUtilsError(
|
|
938
|
+
`Output file already exists: ${outputPath}. Use --force to overwrite it.`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
fs2.mkdirSync(path3.dirname(outputPath), { recursive: true });
|
|
942
|
+
}
|
|
943
|
+
function createZipExecutionPlan(sourcePath, positionalOut, options = {}) {
|
|
944
|
+
if (positionalOut && options.out) {
|
|
945
|
+
throw new BRUtilsError("Use either positional [out] or --out, not both.");
|
|
946
|
+
}
|
|
947
|
+
const resolvedOutputPath = path3.resolve(
|
|
948
|
+
resolveZipOutputPath(sourcePath, positionalOut ?? options.out)
|
|
949
|
+
);
|
|
950
|
+
return {
|
|
951
|
+
sourcePath: path3.resolve(sourcePath),
|
|
952
|
+
outputPath: resolvedOutputPath,
|
|
953
|
+
sourceType: getSourceType(path3.resolve(sourcePath)),
|
|
954
|
+
exclude: options.exclude ?? [],
|
|
955
|
+
contentsOnly: options.contentsOnly ?? false,
|
|
956
|
+
level: resolveCompressionLevel(options.level),
|
|
957
|
+
followSymlinks: options.followSymlinks ?? false,
|
|
958
|
+
store: options.store ?? false
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
async function createZip(sourcePath, positionalOut, options = {}) {
|
|
962
|
+
const plan = createZipExecutionPlan(sourcePath, positionalOut, options);
|
|
963
|
+
if (options.dryRun) {
|
|
964
|
+
return plan;
|
|
965
|
+
}
|
|
966
|
+
ensureOutputCanBeWritten(plan.outputPath, options.force);
|
|
967
|
+
const outputStream = fs2.createWriteStream(plan.outputPath);
|
|
968
|
+
const archive = archiver("zip", {
|
|
969
|
+
zlib: { level: plan.store ? 0 : plan.level },
|
|
970
|
+
store: plan.store
|
|
971
|
+
});
|
|
972
|
+
await new Promise((resolve, reject) => {
|
|
973
|
+
outputStream.on("close", () => resolve());
|
|
974
|
+
archive.on("error", (error) => reject(error));
|
|
975
|
+
archive.pipe(outputStream);
|
|
976
|
+
const inputs = collectZipInputs(plan.sourcePath, plan.outputPath, options);
|
|
977
|
+
for (const input of inputs) {
|
|
978
|
+
if (options.verbose && !options.quiet) {
|
|
979
|
+
console.log(`[zip] adding ${input.entryName}`);
|
|
980
|
+
}
|
|
981
|
+
if (input.type === "directory") {
|
|
982
|
+
archive.append("", { name: input.entryName });
|
|
983
|
+
} else {
|
|
984
|
+
archive.file(input.sourcePath, { name: input.entryName });
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
archive.finalize().catch(reject);
|
|
988
|
+
});
|
|
989
|
+
return plan;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// src/services/zip/list-zip.ts
|
|
993
|
+
import path5 from "path";
|
|
994
|
+
|
|
995
|
+
// src/services/archive/zip-archive.ts
|
|
996
|
+
import fs3 from "fs";
|
|
997
|
+
import path4 from "path";
|
|
998
|
+
import { Writable } from "stream";
|
|
999
|
+
import { pipeline } from "stream/promises";
|
|
1000
|
+
import yauzl from "yauzl";
|
|
1001
|
+
|
|
1002
|
+
// src/core/utils/glob.ts
|
|
1003
|
+
import * as minimatchModule from "minimatch";
|
|
1004
|
+
function resolveMinimatch() {
|
|
1005
|
+
const moduleValue = minimatchModule;
|
|
1006
|
+
if (typeof moduleValue.minimatch === "function") {
|
|
1007
|
+
return moduleValue.minimatch;
|
|
1008
|
+
}
|
|
1009
|
+
if (typeof moduleValue.default === "function") {
|
|
1010
|
+
return moduleValue.default;
|
|
1011
|
+
}
|
|
1012
|
+
if (typeof moduleValue === "function") {
|
|
1013
|
+
return moduleValue;
|
|
1014
|
+
}
|
|
1015
|
+
throw new Error("Could not resolve minimatch implementation.");
|
|
1016
|
+
}
|
|
1017
|
+
var minimatch = resolveMinimatch();
|
|
1018
|
+
function matchesGlob(input, pattern) {
|
|
1019
|
+
return minimatch(input, pattern, { dot: true });
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// src/services/archive/zip-archive.ts
|
|
1023
|
+
function normalizeEntryPath(entryPath) {
|
|
1024
|
+
return entryPath.replace(/\\/g, "/");
|
|
1025
|
+
}
|
|
1026
|
+
function ensureZipSourceIsValid(sourcePath) {
|
|
1027
|
+
if (!fs3.existsSync(sourcePath)) {
|
|
1028
|
+
throw new BRUtilsError(`Zip file does not exist: ${sourcePath}`);
|
|
1029
|
+
}
|
|
1030
|
+
if (!fs3.statSync(sourcePath).isFile()) {
|
|
1031
|
+
throw new BRUtilsError(`Source is not a file: ${sourcePath}`);
|
|
1032
|
+
}
|
|
1033
|
+
if (path4.extname(sourcePath).toLowerCase() !== ".zip") {
|
|
1034
|
+
throw new BRUtilsError("Only .zip files are supported.");
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function openZipFile(sourcePath) {
|
|
1038
|
+
ensureZipSourceIsValid(sourcePath);
|
|
1039
|
+
return new Promise((resolve, reject) => {
|
|
1040
|
+
yauzl.open(
|
|
1041
|
+
sourcePath,
|
|
1042
|
+
{
|
|
1043
|
+
lazyEntries: true,
|
|
1044
|
+
decodeStrings: true,
|
|
1045
|
+
validateEntrySizes: true
|
|
1046
|
+
},
|
|
1047
|
+
(error, zipFile) => {
|
|
1048
|
+
if (error) {
|
|
1049
|
+
reject(error);
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
if (!zipFile) {
|
|
1053
|
+
reject(new BRUtilsError("Could not open zip file."));
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
resolve(zipFile);
|
|
1057
|
+
}
|
|
1058
|
+
);
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
function shouldIncludeEntry(entryPath, match) {
|
|
1062
|
+
if (!match) {
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
return matchesGlob(normalizeEntryPath(entryPath), match);
|
|
1066
|
+
}
|
|
1067
|
+
async function listZipArchiveEntries(sourcePath, options = {}) {
|
|
1068
|
+
const zipFile = await openZipFile(sourcePath);
|
|
1069
|
+
return new Promise((resolve, reject) => {
|
|
1070
|
+
const entries = [];
|
|
1071
|
+
zipFile.on("entry", (entry) => {
|
|
1072
|
+
const entryPath = normalizeEntryPath(entry.fileName);
|
|
1073
|
+
if (shouldIncludeEntry(entryPath, options.match)) {
|
|
1074
|
+
entries.push({
|
|
1075
|
+
path: entryPath,
|
|
1076
|
+
type: entryPath.endsWith("/") ? "directory" : "file",
|
|
1077
|
+
compressedSize: entry.compressedSize,
|
|
1078
|
+
uncompressedSize: entry.uncompressedSize
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
zipFile.readEntry();
|
|
1082
|
+
});
|
|
1083
|
+
zipFile.once("end", () => {
|
|
1084
|
+
zipFile.close();
|
|
1085
|
+
resolve(entries);
|
|
1086
|
+
});
|
|
1087
|
+
zipFile.once("error", (error) => {
|
|
1088
|
+
zipFile.close();
|
|
1089
|
+
reject(error);
|
|
1090
|
+
});
|
|
1091
|
+
zipFile.readEntry();
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
async function drainEntryStream(zipFile, entry) {
|
|
1095
|
+
await new Promise((resolve, reject) => {
|
|
1096
|
+
zipFile.openReadStream(entry, async (error, stream) => {
|
|
1097
|
+
if (error) {
|
|
1098
|
+
reject(error);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
if (!stream) {
|
|
1102
|
+
reject(
|
|
1103
|
+
new BRUtilsError(`Could not read archive entry: ${entry.fileName}`)
|
|
1104
|
+
);
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
try {
|
|
1108
|
+
await pipeline(
|
|
1109
|
+
stream,
|
|
1110
|
+
new Writable({
|
|
1111
|
+
write(_chunk, _encoding, callback) {
|
|
1112
|
+
callback();
|
|
1113
|
+
}
|
|
1114
|
+
})
|
|
1115
|
+
);
|
|
1116
|
+
resolve();
|
|
1117
|
+
} catch (streamError) {
|
|
1118
|
+
reject(streamError);
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
async function testZipArchive(sourcePath, options = {}) {
|
|
1124
|
+
const zipFile = await openZipFile(sourcePath);
|
|
1125
|
+
let testedEntries = 0;
|
|
1126
|
+
return new Promise((resolve, reject) => {
|
|
1127
|
+
zipFile.on("entry", (entry) => {
|
|
1128
|
+
const entryPath = normalizeEntryPath(entry.fileName);
|
|
1129
|
+
void (async () => {
|
|
1130
|
+
try {
|
|
1131
|
+
if (!shouldIncludeEntry(entryPath, options.match) || entryPath.endsWith("/")) {
|
|
1132
|
+
zipFile.readEntry();
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
await drainEntryStream(zipFile, entry);
|
|
1136
|
+
testedEntries += 1;
|
|
1137
|
+
zipFile.readEntry();
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
zipFile.close();
|
|
1140
|
+
reject(error);
|
|
1141
|
+
}
|
|
1142
|
+
})();
|
|
1143
|
+
});
|
|
1144
|
+
zipFile.once("end", () => {
|
|
1145
|
+
zipFile.close();
|
|
1146
|
+
resolve({
|
|
1147
|
+
sourcePath: path4.resolve(sourcePath),
|
|
1148
|
+
testedEntries,
|
|
1149
|
+
ok: true
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
zipFile.once("error", (error) => {
|
|
1153
|
+
zipFile.close();
|
|
1154
|
+
reject(error);
|
|
1155
|
+
});
|
|
1156
|
+
zipFile.readEntry();
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// src/services/zip/list-zip.ts
|
|
1161
|
+
async function listZip(sourcePath, match) {
|
|
1162
|
+
return listZipArchiveEntries(
|
|
1163
|
+
path5.resolve(sourcePath),
|
|
1164
|
+
match ? { match } : {}
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/services/zip/test-zip.ts
|
|
1169
|
+
import path6 from "path";
|
|
1170
|
+
async function testZip(sourcePath, match) {
|
|
1171
|
+
return testZipArchive(path6.resolve(sourcePath), match ? { match } : {});
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// src/services/unzip/resolve-unzip-output.ts
|
|
1175
|
+
import path7 from "path";
|
|
1176
|
+
function resolveUnzipOutputPath(sourcePath, explicitOut) {
|
|
1177
|
+
if (explicitOut) {
|
|
1178
|
+
return explicitOut;
|
|
1179
|
+
}
|
|
1180
|
+
const resolvedSource = path7.resolve(sourcePath);
|
|
1181
|
+
const parsed = path7.parse(resolvedSource);
|
|
1182
|
+
return path7.join(parsed.dir, parsed.name);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/services/unzip/extract-zip.ts
|
|
1186
|
+
import fs4 from "fs";
|
|
1187
|
+
import path8 from "path";
|
|
1188
|
+
import { pipeline as pipeline2 } from "stream/promises";
|
|
1189
|
+
import yauzl2 from "yauzl";
|
|
1190
|
+
function ensureOutputCanBeExtracted(outputDir, force = false) {
|
|
1191
|
+
if (fs4.existsSync(outputDir) && !force) {
|
|
1192
|
+
throw new BRUtilsError(
|
|
1193
|
+
`Output directory already exists: ${outputDir}. Use --force to overwrite it.`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
if (fs4.existsSync(outputDir) && force) {
|
|
1197
|
+
fs4.rmSync(outputDir, { recursive: true, force: true });
|
|
1198
|
+
}
|
|
1199
|
+
fs4.mkdirSync(outputDir, { recursive: true });
|
|
1200
|
+
}
|
|
1201
|
+
function normalizeEntryPath2(entryPath) {
|
|
1202
|
+
return entryPath.replace(/\\/g, "/");
|
|
1203
|
+
}
|
|
1204
|
+
function shouldIncludeEntry2(entryPath, match) {
|
|
1205
|
+
if (!match) {
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1208
|
+
return matchesGlob(normalizeEntryPath2(entryPath), match);
|
|
1209
|
+
}
|
|
1210
|
+
function resolveTargetRelativePath(entryPath, flat) {
|
|
1211
|
+
if (!flat) {
|
|
1212
|
+
return normalizeEntryPath2(entryPath);
|
|
1213
|
+
}
|
|
1214
|
+
return path8.posix.basename(normalizeEntryPath2(entryPath));
|
|
1215
|
+
}
|
|
1216
|
+
function resolveSafeDestination(outputDir, relativePath) {
|
|
1217
|
+
const destination = path8.resolve(outputDir, relativePath);
|
|
1218
|
+
const resolvedOutputDir = path8.resolve(outputDir);
|
|
1219
|
+
const outputPrefix = `${resolvedOutputDir}${path8.sep}`;
|
|
1220
|
+
if (destination !== resolvedOutputDir && !destination.startsWith(outputPrefix)) {
|
|
1221
|
+
throw new BRUtilsError(`Unsafe zip entry path detected: ${relativePath}`);
|
|
1222
|
+
}
|
|
1223
|
+
return destination;
|
|
1224
|
+
}
|
|
1225
|
+
function openZipFile2(sourcePath) {
|
|
1226
|
+
ensureZipSourceIsValid(sourcePath);
|
|
1227
|
+
return new Promise((resolve, reject) => {
|
|
1228
|
+
yauzl2.open(
|
|
1229
|
+
sourcePath,
|
|
1230
|
+
{
|
|
1231
|
+
lazyEntries: true,
|
|
1232
|
+
decodeStrings: true,
|
|
1233
|
+
validateEntrySizes: true
|
|
1234
|
+
},
|
|
1235
|
+
(error, zipFile) => {
|
|
1236
|
+
if (error) {
|
|
1237
|
+
reject(error);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (!zipFile) {
|
|
1241
|
+
reject(new BRUtilsError("Could not open zip file."));
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
resolve(zipFile);
|
|
1245
|
+
}
|
|
1246
|
+
);
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
function createUnzipExecutionPlan(sourcePath, positionalOut, options = {}) {
|
|
1250
|
+
if (positionalOut && options.out) {
|
|
1251
|
+
throw new BRUtilsError("Use either positional [out] or --out, not both.");
|
|
1252
|
+
}
|
|
1253
|
+
const resolvedSourcePath = path8.resolve(sourcePath);
|
|
1254
|
+
const resolvedOutputDir = path8.resolve(
|
|
1255
|
+
resolveUnzipOutputPath(sourcePath, positionalOut ?? options.out)
|
|
1256
|
+
);
|
|
1257
|
+
return {
|
|
1258
|
+
sourcePath: resolvedSourcePath,
|
|
1259
|
+
outputDir: resolvedOutputDir,
|
|
1260
|
+
flat: options.flat ?? false,
|
|
1261
|
+
...options.match ? { match: options.match } : {}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
async function extractZipFile(sourcePath, positionalOut, options = {}) {
|
|
1265
|
+
const plan = createUnzipExecutionPlan(sourcePath, positionalOut, options);
|
|
1266
|
+
ensureZipSourceIsValid(plan.sourcePath);
|
|
1267
|
+
if (options.dryRun) {
|
|
1268
|
+
return plan;
|
|
1269
|
+
}
|
|
1270
|
+
ensureOutputCanBeExtracted(plan.outputDir, options.force);
|
|
1271
|
+
if (options.verbose && !options.quiet) {
|
|
1272
|
+
console.log(`[unzip] extracting ${plan.sourcePath} -> ${plan.outputDir}`);
|
|
1273
|
+
}
|
|
1274
|
+
const zipFile = await openZipFile2(plan.sourcePath);
|
|
1275
|
+
const flatTargets = /* @__PURE__ */ new Set();
|
|
1276
|
+
await new Promise((resolve, reject) => {
|
|
1277
|
+
zipFile.on("entry", (entry) => {
|
|
1278
|
+
const entryPath = normalizeEntryPath2(entry.fileName);
|
|
1279
|
+
void (async () => {
|
|
1280
|
+
try {
|
|
1281
|
+
if (!shouldIncludeEntry2(entryPath, options.match)) {
|
|
1282
|
+
zipFile.readEntry();
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
const isDirectory2 = entryPath.endsWith("/");
|
|
1286
|
+
const targetRelativePath = resolveTargetRelativePath(
|
|
1287
|
+
entryPath,
|
|
1288
|
+
options.flat ?? false
|
|
1289
|
+
);
|
|
1290
|
+
if (!targetRelativePath) {
|
|
1291
|
+
zipFile.readEntry();
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
if ((options.flat ?? false) && flatTargets.has(targetRelativePath) && !isDirectory2) {
|
|
1295
|
+
throw new BRUtilsError(
|
|
1296
|
+
`Flat extraction would overwrite a file more than once: ${targetRelativePath}`
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
if ((options.flat ?? false) && !isDirectory2) {
|
|
1300
|
+
flatTargets.add(targetRelativePath);
|
|
1301
|
+
}
|
|
1302
|
+
const destinationPath = resolveSafeDestination(
|
|
1303
|
+
plan.outputDir,
|
|
1304
|
+
targetRelativePath
|
|
1305
|
+
);
|
|
1306
|
+
if (options.verbose && !options.quiet) {
|
|
1307
|
+
console.log(`[unzip] extracting ${entryPath}`);
|
|
1308
|
+
}
|
|
1309
|
+
if (isDirectory2) {
|
|
1310
|
+
if (!(options.flat ?? false)) {
|
|
1311
|
+
fs4.mkdirSync(destinationPath, { recursive: true });
|
|
1312
|
+
}
|
|
1313
|
+
zipFile.readEntry();
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
fs4.mkdirSync(path8.dirname(destinationPath), { recursive: true });
|
|
1317
|
+
await new Promise((entryResolve, entryReject) => {
|
|
1318
|
+
zipFile.openReadStream(entry, async (error, stream) => {
|
|
1319
|
+
if (error) {
|
|
1320
|
+
entryReject(error);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
if (!stream) {
|
|
1324
|
+
entryReject(
|
|
1325
|
+
new BRUtilsError(
|
|
1326
|
+
`Could not read archive entry: ${entry.fileName}`
|
|
1327
|
+
)
|
|
1328
|
+
);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
try {
|
|
1332
|
+
await pipeline2(stream, fs4.createWriteStream(destinationPath));
|
|
1333
|
+
entryResolve();
|
|
1334
|
+
} catch (streamError) {
|
|
1335
|
+
entryReject(streamError);
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
});
|
|
1339
|
+
zipFile.readEntry();
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
zipFile.close();
|
|
1342
|
+
reject(error);
|
|
1343
|
+
}
|
|
1344
|
+
})();
|
|
1345
|
+
});
|
|
1346
|
+
zipFile.once("end", () => {
|
|
1347
|
+
zipFile.close();
|
|
1348
|
+
resolve();
|
|
1349
|
+
});
|
|
1350
|
+
zipFile.once("error", (error) => {
|
|
1351
|
+
zipFile.close();
|
|
1352
|
+
reject(error);
|
|
1353
|
+
});
|
|
1354
|
+
zipFile.readEntry();
|
|
1355
|
+
});
|
|
1356
|
+
return plan;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// src/services/unzip/list-unzip.ts
|
|
1360
|
+
import path9 from "path";
|
|
1361
|
+
async function listUnzip(sourcePath, match) {
|
|
1362
|
+
return listZipArchiveEntries(
|
|
1363
|
+
path9.resolve(sourcePath),
|
|
1364
|
+
match ? { match } : {}
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// src/services/unzip/test-unzip.ts
|
|
1369
|
+
import path10 from "path";
|
|
1370
|
+
async function testUnzip(sourcePath, match) {
|
|
1371
|
+
return testZipArchive(path10.resolve(sourcePath), match ? { match } : {});
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// src/services/str/str.service.ts
|
|
1375
|
+
function ensureNonNegativeInteger(value, label) {
|
|
1376
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1377
|
+
throw new BRUtilsError(`${label} must be a non-negative integer.`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
function splitWords(value) {
|
|
1381
|
+
return value.replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/[_\-.]+/g, " ").trim().split(/\s+/).filter(Boolean).map((word) => word.toLowerCase());
|
|
1382
|
+
}
|
|
1383
|
+
function capitalize(word) {
|
|
1384
|
+
if (word.length === 0) {
|
|
1385
|
+
return word;
|
|
1386
|
+
}
|
|
1387
|
+
return word[0].toUpperCase() + word.slice(1).toLowerCase();
|
|
1388
|
+
}
|
|
1389
|
+
function encodeHtmlEntities(value) {
|
|
1390
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1391
|
+
}
|
|
1392
|
+
function decodeHtmlEntities(value) {
|
|
1393
|
+
return value.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
1394
|
+
}
|
|
1395
|
+
function buildGlobalRegex(pattern) {
|
|
1396
|
+
try {
|
|
1397
|
+
return new RegExp(pattern, "g");
|
|
1398
|
+
} catch {
|
|
1399
|
+
throw new BRUtilsError(`Invalid regex pattern: ${pattern}`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
function extractUsingDelimiters(value, query) {
|
|
1403
|
+
const separatorIndex = query.indexOf("|");
|
|
1404
|
+
if (separatorIndex === -1) {
|
|
1405
|
+
throw new BRUtilsError(
|
|
1406
|
+
'Delimiter extraction expects a query in the format "start|end".'
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
const startDelimiter = query.slice(0, separatorIndex);
|
|
1410
|
+
const endDelimiter = query.slice(separatorIndex + 1);
|
|
1411
|
+
if (startDelimiter.length === 0 || endDelimiter.length === 0) {
|
|
1412
|
+
throw new BRUtilsError(
|
|
1413
|
+
"Delimiter extraction expects non-empty start and end delimiters."
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
const matches = [];
|
|
1417
|
+
let cursor = 0;
|
|
1418
|
+
while (cursor < value.length) {
|
|
1419
|
+
const startIndex = value.indexOf(startDelimiter, cursor);
|
|
1420
|
+
if (startIndex === -1) {
|
|
1421
|
+
break;
|
|
1422
|
+
}
|
|
1423
|
+
const contentStart = startIndex + startDelimiter.length;
|
|
1424
|
+
const endIndex = value.indexOf(endDelimiter, contentStart);
|
|
1425
|
+
if (endIndex === -1) {
|
|
1426
|
+
break;
|
|
1427
|
+
}
|
|
1428
|
+
matches.push(value.slice(contentStart, endIndex));
|
|
1429
|
+
cursor = endIndex + endDelimiter.length;
|
|
1430
|
+
}
|
|
1431
|
+
return matches;
|
|
1432
|
+
}
|
|
1433
|
+
function slugifyText(value) {
|
|
1434
|
+
return removeAccents(value).toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
|
|
1435
|
+
}
|
|
1436
|
+
function convertStringCase(value, style) {
|
|
1437
|
+
const words = splitWords(value);
|
|
1438
|
+
if (words.length === 0) {
|
|
1439
|
+
return "";
|
|
1440
|
+
}
|
|
1441
|
+
switch (style) {
|
|
1442
|
+
case "camel":
|
|
1443
|
+
return words[0] + words.slice(1).map(capitalize).join("");
|
|
1444
|
+
case "snake":
|
|
1445
|
+
return words.join("_");
|
|
1446
|
+
case "kebab":
|
|
1447
|
+
return words.join("-");
|
|
1448
|
+
case "pascal":
|
|
1449
|
+
return words.map(capitalize).join("");
|
|
1450
|
+
case "constant":
|
|
1451
|
+
return words.join("_").toUpperCase();
|
|
1452
|
+
case "title":
|
|
1453
|
+
return words.map(capitalize).join(" ");
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
function trimText(value) {
|
|
1457
|
+
return value.trim();
|
|
1458
|
+
}
|
|
1459
|
+
function truncateText(value, options) {
|
|
1460
|
+
ensureNonNegativeInteger(options.max, "Maximum length");
|
|
1461
|
+
if (value.length <= options.max) {
|
|
1462
|
+
return value;
|
|
1463
|
+
}
|
|
1464
|
+
const suffix = options.suffix ?? "";
|
|
1465
|
+
if (suffix.length >= options.max) {
|
|
1466
|
+
return suffix.slice(0, options.max);
|
|
1467
|
+
}
|
|
1468
|
+
return value.slice(0, options.max - suffix.length) + suffix;
|
|
1469
|
+
}
|
|
1470
|
+
function replaceText(value, options) {
|
|
1471
|
+
if (options.regex) {
|
|
1472
|
+
return value.replace(buildGlobalRegex(options.from), options.with);
|
|
1473
|
+
}
|
|
1474
|
+
return value.replaceAll(options.from, options.with);
|
|
1475
|
+
}
|
|
1476
|
+
function normalizeText(value) {
|
|
1477
|
+
return value.normalize("NFC");
|
|
1478
|
+
}
|
|
1479
|
+
function removeAccents(value) {
|
|
1480
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, "");
|
|
1481
|
+
}
|
|
1482
|
+
function padText(value, options) {
|
|
1483
|
+
ensureNonNegativeInteger(options.length, "Target length");
|
|
1484
|
+
const side = options.side ?? "right";
|
|
1485
|
+
if (value.length >= options.length) {
|
|
1486
|
+
return value;
|
|
1487
|
+
}
|
|
1488
|
+
switch (side) {
|
|
1489
|
+
case "left":
|
|
1490
|
+
return value.padStart(options.length);
|
|
1491
|
+
case "right":
|
|
1492
|
+
return value.padEnd(options.length);
|
|
1493
|
+
case "both": {
|
|
1494
|
+
const totalPadding = options.length - value.length;
|
|
1495
|
+
const leftPadding = Math.floor(totalPadding / 2);
|
|
1496
|
+
const rightPadding = totalPadding - leftPadding;
|
|
1497
|
+
return `${" ".repeat(leftPadding)}${value}${" ".repeat(rightPadding)}`;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
function extractText(value, query, options = {}) {
|
|
1502
|
+
if (options.regex) {
|
|
1503
|
+
const matches = Array.from(value.matchAll(buildGlobalRegex(query)));
|
|
1504
|
+
return matches.flatMap((match) => {
|
|
1505
|
+
const groups = match.slice(1).filter((group) => group !== void 0);
|
|
1506
|
+
return groups.length > 0 ? groups : [match[0]];
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
return extractUsingDelimiters(value, query);
|
|
1510
|
+
}
|
|
1511
|
+
function transformBase64(value, mode = "encode") {
|
|
1512
|
+
if (mode === "decode") {
|
|
1513
|
+
return Buffer.from(value, "base64").toString("utf-8");
|
|
1514
|
+
}
|
|
1515
|
+
return Buffer.from(value, "utf-8").toString("base64");
|
|
1516
|
+
}
|
|
1517
|
+
function transformUrlEncoding(value, mode = "encode") {
|
|
1518
|
+
if (mode === "decode") {
|
|
1519
|
+
return decodeURIComponent(value);
|
|
1520
|
+
}
|
|
1521
|
+
return encodeURIComponent(value);
|
|
1522
|
+
}
|
|
1523
|
+
function transformHtmlEntities(value, mode = "encode") {
|
|
1524
|
+
if (mode === "decode") {
|
|
1525
|
+
return decodeHtmlEntities(value);
|
|
1526
|
+
}
|
|
1527
|
+
return encodeHtmlEntities(value);
|
|
1528
|
+
}
|
|
1529
|
+
function getLevenshteinDistance(a, b) {
|
|
1530
|
+
a = a.trim();
|
|
1531
|
+
b = b.trim();
|
|
1532
|
+
const n = a.length;
|
|
1533
|
+
const m = b.length;
|
|
1534
|
+
const dp = Array.from(
|
|
1535
|
+
{ length: n + 1 },
|
|
1536
|
+
() => new Array(m + 1).fill(0)
|
|
1537
|
+
);
|
|
1538
|
+
for (let i = 0; i <= n; i++) dp[i][0] = i;
|
|
1539
|
+
for (let j = 0; j <= m; j++) dp[0][j] = j;
|
|
1540
|
+
for (let i = 1; i <= n; i++) {
|
|
1541
|
+
for (let j = 1; j <= m; j++) {
|
|
1542
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1543
|
+
dp[i][j] = Math.min(
|
|
1544
|
+
dp[i - 1][j] + 1,
|
|
1545
|
+
dp[i][j - 1] + 1,
|
|
1546
|
+
dp[i - 1][j - 1] + cost
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
return dp[n][m];
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// src/services/json/json.service.ts
|
|
1554
|
+
function isPlainObject(value) {
|
|
1555
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1556
|
+
}
|
|
1557
|
+
function cloneJsonValue(value) {
|
|
1558
|
+
return JSON.parse(JSON.stringify(value));
|
|
1559
|
+
}
|
|
1560
|
+
function sortKeysDeep(value) {
|
|
1561
|
+
if (Array.isArray(value)) {
|
|
1562
|
+
return value.map((item) => sortKeysDeep(item));
|
|
1563
|
+
}
|
|
1564
|
+
if (isPlainObject(value)) {
|
|
1565
|
+
return Object.keys(value).sort((left, right) => left.localeCompare(right)).reduce((accumulator, key) => {
|
|
1566
|
+
accumulator[key] = sortKeysDeep(value[key]);
|
|
1567
|
+
return accumulator;
|
|
1568
|
+
}, {});
|
|
1569
|
+
}
|
|
1570
|
+
return value;
|
|
1571
|
+
}
|
|
1572
|
+
function formatYamlScalar(value) {
|
|
1573
|
+
if (value === null) {
|
|
1574
|
+
return "null";
|
|
1575
|
+
}
|
|
1576
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1577
|
+
return String(value);
|
|
1578
|
+
}
|
|
1579
|
+
if (typeof value === "string") {
|
|
1580
|
+
if (/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
1581
|
+
return value;
|
|
1582
|
+
}
|
|
1583
|
+
return JSON.stringify(value);
|
|
1584
|
+
}
|
|
1585
|
+
return JSON.stringify(value);
|
|
1586
|
+
}
|
|
1587
|
+
function toYamlLines(value, depth = 0) {
|
|
1588
|
+
const indent = " ".repeat(depth);
|
|
1589
|
+
if (Array.isArray(value)) {
|
|
1590
|
+
if (value.length === 0) {
|
|
1591
|
+
return [`${indent}[]`];
|
|
1592
|
+
}
|
|
1593
|
+
return value.flatMap((item) => {
|
|
1594
|
+
if (Array.isArray(item) || isPlainObject(item)) {
|
|
1595
|
+
const nested = toYamlLines(item, depth + 1);
|
|
1596
|
+
return [`${indent}-`, ...nested];
|
|
1597
|
+
}
|
|
1598
|
+
return [`${indent}- ${formatYamlScalar(item)}`];
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
if (isPlainObject(value)) {
|
|
1602
|
+
const keys = Object.keys(value);
|
|
1603
|
+
if (keys.length === 0) {
|
|
1604
|
+
return [`${indent}{}`];
|
|
1605
|
+
}
|
|
1606
|
+
return keys.flatMap((key) => {
|
|
1607
|
+
const child = value[key];
|
|
1608
|
+
if (Array.isArray(child) || isPlainObject(child)) {
|
|
1609
|
+
return [`${indent}${key}:`, ...toYamlLines(child, depth + 1)];
|
|
1610
|
+
}
|
|
1611
|
+
return [`${indent}${key}: ${formatYamlScalar(child)}`];
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
return [`${indent}${formatYamlScalar(value)}`];
|
|
1615
|
+
}
|
|
1616
|
+
function parsePath(path11) {
|
|
1617
|
+
const segments = path11.match(/[^.[\]]+/g) ?? [];
|
|
1618
|
+
if (segments.length === 0) {
|
|
1619
|
+
throw new BRUtilsError("JSON path cannot be empty.");
|
|
1620
|
+
}
|
|
1621
|
+
return segments;
|
|
1622
|
+
}
|
|
1623
|
+
function getContainerForPath(root, segments) {
|
|
1624
|
+
let current = root;
|
|
1625
|
+
for (const segment of segments) {
|
|
1626
|
+
if (Array.isArray(current)) {
|
|
1627
|
+
const index = Number(segment);
|
|
1628
|
+
if (!Number.isInteger(index)) {
|
|
1629
|
+
return void 0;
|
|
1630
|
+
}
|
|
1631
|
+
current = current[index];
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
if (isPlainObject(current)) {
|
|
1635
|
+
current = current[segment];
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
return void 0;
|
|
1639
|
+
}
|
|
1640
|
+
return current;
|
|
1641
|
+
}
|
|
1642
|
+
function appendDiffPath(basePath, segment) {
|
|
1643
|
+
if (typeof segment === "number") {
|
|
1644
|
+
return `${basePath}[${segment}]`;
|
|
1645
|
+
}
|
|
1646
|
+
return basePath === "$" ? `$.${segment}` : `${basePath}.${segment}`;
|
|
1647
|
+
}
|
|
1648
|
+
function deepEqual(left, right) {
|
|
1649
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
1650
|
+
}
|
|
1651
|
+
function diffValues(left, right, basePath = "$") {
|
|
1652
|
+
if (deepEqual(left, right)) {
|
|
1653
|
+
return [];
|
|
1654
|
+
}
|
|
1655
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
1656
|
+
const result = [];
|
|
1657
|
+
const maxLength = Math.max(left.length, right.length);
|
|
1658
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
1659
|
+
if (index >= left.length) {
|
|
1660
|
+
result.push({
|
|
1661
|
+
path: appendDiffPath(basePath, index),
|
|
1662
|
+
type: "added",
|
|
1663
|
+
right: right[index]
|
|
1664
|
+
});
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
if (index >= right.length) {
|
|
1668
|
+
result.push({
|
|
1669
|
+
path: appendDiffPath(basePath, index),
|
|
1670
|
+
type: "removed",
|
|
1671
|
+
left: left[index]
|
|
1672
|
+
});
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
result.push(
|
|
1676
|
+
...diffValues(
|
|
1677
|
+
left[index],
|
|
1678
|
+
right[index],
|
|
1679
|
+
appendDiffPath(basePath, index)
|
|
1680
|
+
)
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
return result;
|
|
1684
|
+
}
|
|
1685
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
1686
|
+
const result = [];
|
|
1687
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
1688
|
+
for (const key of Array.from(keys).sort((a, b) => a.localeCompare(b))) {
|
|
1689
|
+
if (!(key in left)) {
|
|
1690
|
+
result.push({
|
|
1691
|
+
path: appendDiffPath(basePath, key),
|
|
1692
|
+
type: "added",
|
|
1693
|
+
right: right[key]
|
|
1694
|
+
});
|
|
1695
|
+
continue;
|
|
1696
|
+
}
|
|
1697
|
+
if (!(key in right)) {
|
|
1698
|
+
result.push({
|
|
1699
|
+
path: appendDiffPath(basePath, key),
|
|
1700
|
+
type: "removed",
|
|
1701
|
+
left: left[key]
|
|
1702
|
+
});
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1705
|
+
result.push(
|
|
1706
|
+
...diffValues(left[key], right[key], appendDiffPath(basePath, key))
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
return result;
|
|
1710
|
+
}
|
|
1711
|
+
return [
|
|
1712
|
+
{
|
|
1713
|
+
path: basePath,
|
|
1714
|
+
type: "changed",
|
|
1715
|
+
left,
|
|
1716
|
+
right
|
|
1717
|
+
}
|
|
1718
|
+
];
|
|
1719
|
+
}
|
|
1720
|
+
function deepMerge(left, right) {
|
|
1721
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
1722
|
+
return cloneJsonValue(right);
|
|
1723
|
+
}
|
|
1724
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
1725
|
+
const result = cloneJsonValue(left);
|
|
1726
|
+
for (const [key, value] of Object.entries(right)) {
|
|
1727
|
+
if (key in result) {
|
|
1728
|
+
result[key] = deepMerge(result[key], value);
|
|
1729
|
+
} else {
|
|
1730
|
+
result[key] = cloneJsonValue(value);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
return result;
|
|
1734
|
+
}
|
|
1735
|
+
return cloneJsonValue(right);
|
|
1736
|
+
}
|
|
1737
|
+
function parseJsonInput(value) {
|
|
1738
|
+
try {
|
|
1739
|
+
return JSON.parse(value);
|
|
1740
|
+
} catch (error) {
|
|
1741
|
+
const message = error instanceof Error ? error.message : "Unknown JSON parse error.";
|
|
1742
|
+
throw new BRUtilsError(message);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
function validateJsonInput(value) {
|
|
1746
|
+
try {
|
|
1747
|
+
JSON.parse(value);
|
|
1748
|
+
return { isValid: true };
|
|
1749
|
+
} catch (error) {
|
|
1750
|
+
const message = error instanceof Error ? error.message : "Unknown JSON parse error.";
|
|
1751
|
+
return { isValid: false, error: message };
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
function formatJsonValue(value, indent = 2, sortKeys = false) {
|
|
1755
|
+
if (!Number.isInteger(indent) || indent < 0) {
|
|
1756
|
+
throw new BRUtilsError("Indent must be a non-negative integer.");
|
|
1757
|
+
}
|
|
1758
|
+
const normalized = sortKeys ? sortKeysDeep(value) : value;
|
|
1759
|
+
return JSON.stringify(normalized, null, indent);
|
|
1760
|
+
}
|
|
1761
|
+
function minifyJsonValue(value) {
|
|
1762
|
+
return JSON.stringify(value);
|
|
1763
|
+
}
|
|
1764
|
+
function getJsonPathValue(value, path11) {
|
|
1765
|
+
return getContainerForPath(value, parsePath(path11));
|
|
1766
|
+
}
|
|
1767
|
+
function setJsonPathValue(value, path11, newValue) {
|
|
1768
|
+
const root = cloneJsonValue(value);
|
|
1769
|
+
const segments = parsePath(path11);
|
|
1770
|
+
let current = root;
|
|
1771
|
+
segments.forEach((segment, index) => {
|
|
1772
|
+
const isLast = index === segments.length - 1;
|
|
1773
|
+
const nextSegment = segments[index + 1];
|
|
1774
|
+
const nextShouldBeArray = nextSegment !== void 0 && /^\d+$/.test(nextSegment);
|
|
1775
|
+
if (Array.isArray(current)) {
|
|
1776
|
+
const currentIndex = Number(segment);
|
|
1777
|
+
if (!Number.isInteger(currentIndex)) {
|
|
1778
|
+
throw new BRUtilsError(`Invalid array index in path: ${segment}`);
|
|
1779
|
+
}
|
|
1780
|
+
if (isLast) {
|
|
1781
|
+
current[currentIndex] = newValue;
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
if (current[currentIndex] === void 0) {
|
|
1785
|
+
current[currentIndex] = nextShouldBeArray ? [] : {};
|
|
1786
|
+
}
|
|
1787
|
+
current = current[currentIndex];
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
if (!isPlainObject(current)) {
|
|
1791
|
+
throw new BRUtilsError(`Cannot set nested path at segment: ${segment}`);
|
|
1792
|
+
}
|
|
1793
|
+
if (isLast) {
|
|
1794
|
+
current[segment] = newValue;
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
if (current[segment] === void 0) {
|
|
1798
|
+
current[segment] = nextShouldBeArray ? [] : {};
|
|
1799
|
+
}
|
|
1800
|
+
current = current[segment];
|
|
1801
|
+
});
|
|
1802
|
+
return root;
|
|
1803
|
+
}
|
|
1804
|
+
function deleteJsonPathValue(value, path11) {
|
|
1805
|
+
const root = cloneJsonValue(value);
|
|
1806
|
+
const segments = parsePath(path11);
|
|
1807
|
+
const parent = getContainerForPath(root, segments.slice(0, -1));
|
|
1808
|
+
const lastSegment = segments[segments.length - 1];
|
|
1809
|
+
if (Array.isArray(parent)) {
|
|
1810
|
+
const index = Number(lastSegment);
|
|
1811
|
+
if (!Number.isInteger(index)) {
|
|
1812
|
+
throw new BRUtilsError(`Invalid array index in path: ${lastSegment}`);
|
|
1813
|
+
}
|
|
1814
|
+
parent.splice(index, 1);
|
|
1815
|
+
return root;
|
|
1816
|
+
}
|
|
1817
|
+
if (isPlainObject(parent)) {
|
|
1818
|
+
delete parent[lastSegment];
|
|
1819
|
+
return root;
|
|
1820
|
+
}
|
|
1821
|
+
if (segments.length === 1 && isPlainObject(root)) {
|
|
1822
|
+
delete root[lastSegment];
|
|
1823
|
+
return root;
|
|
1824
|
+
}
|
|
1825
|
+
throw new BRUtilsError(`Path not found: ${path11}`);
|
|
1826
|
+
}
|
|
1827
|
+
function diffJsonValues(left, right) {
|
|
1828
|
+
const changes = diffValues(left, right);
|
|
1829
|
+
return {
|
|
1830
|
+
isEqual: changes.length === 0,
|
|
1831
|
+
changes
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
function mergeJsonValues(values) {
|
|
1835
|
+
if (values.length < 2) {
|
|
1836
|
+
throw new BRUtilsError("Merge requires at least two JSON sources.");
|
|
1837
|
+
}
|
|
1838
|
+
return values.slice(1).reduce((accumulator, current) => {
|
|
1839
|
+
return deepMerge(accumulator, current);
|
|
1840
|
+
}, cloneJsonValue(values[0]));
|
|
1841
|
+
}
|
|
1842
|
+
function convertJsonToYaml(value) {
|
|
1843
|
+
return toYamlLines(value).join("\n");
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// src/services/hash/hash.service.ts
|
|
1847
|
+
import crypto from "crypto";
|
|
1848
|
+
import fs5 from "fs";
|
|
1849
|
+
function resolveHashSource(options) {
|
|
1850
|
+
if (options.text !== void 0 && options.file !== void 0) {
|
|
1851
|
+
throw new BRUtilsError("Use either --text or --file, not both.");
|
|
1852
|
+
}
|
|
1853
|
+
if (options.text !== void 0) {
|
|
1854
|
+
return {
|
|
1855
|
+
kind: "text",
|
|
1856
|
+
content: Buffer.from(options.text, "utf-8")
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
if (options.file !== void 0) {
|
|
1860
|
+
if (!fs5.existsSync(options.file) || !fs5.statSync(options.file).isFile()) {
|
|
1861
|
+
throw new BRUtilsError(`File does not exist: ${options.file}`);
|
|
1862
|
+
}
|
|
1863
|
+
return {
|
|
1864
|
+
kind: "file",
|
|
1865
|
+
content: fs5.readFileSync(options.file)
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
throw new BRUtilsError("One of --text or --file is required.");
|
|
1869
|
+
}
|
|
1870
|
+
function normalizeAlgorithm(value) {
|
|
1871
|
+
const algorithm = value.toLowerCase();
|
|
1872
|
+
if (!crypto.getHashes().includes(algorithm)) {
|
|
1873
|
+
throw new BRUtilsError(`Unsupported hash algorithm: ${value}`);
|
|
1874
|
+
}
|
|
1875
|
+
return algorithm;
|
|
1876
|
+
}
|
|
1877
|
+
function digestBuffer(buffer, algorithm) {
|
|
1878
|
+
return crypto.createHash(normalizeAlgorithm(algorithm)).update(buffer).digest("hex");
|
|
1879
|
+
}
|
|
1880
|
+
function normalizeExpectedHash(value) {
|
|
1881
|
+
return value.trim().toLowerCase();
|
|
1882
|
+
}
|
|
1883
|
+
function computeHash(options, algorithm) {
|
|
1884
|
+
const source = resolveHashSource(options);
|
|
1885
|
+
return digestBuffer(source.content, algorithm);
|
|
1886
|
+
}
|
|
1887
|
+
function computeMd5(options) {
|
|
1888
|
+
return computeHash(options, "md5");
|
|
1889
|
+
}
|
|
1890
|
+
function computeSha1(options) {
|
|
1891
|
+
return computeHash(options, "sha1");
|
|
1892
|
+
}
|
|
1893
|
+
function computeSha256(options) {
|
|
1894
|
+
return computeHash(options, "sha256");
|
|
1895
|
+
}
|
|
1896
|
+
function computeSha512(options) {
|
|
1897
|
+
return computeHash(options, "sha512");
|
|
1898
|
+
}
|
|
1899
|
+
function computeHmac(options) {
|
|
1900
|
+
const source = resolveHashSource(options);
|
|
1901
|
+
return crypto.createHmac(normalizeAlgorithm(options.algo), options.key).update(source.content).digest("hex");
|
|
1902
|
+
}
|
|
1903
|
+
function computeChecksum(options) {
|
|
1904
|
+
return computeHash({ file: options.file }, options.algo ?? "sha256");
|
|
1905
|
+
}
|
|
1906
|
+
function compareHash(options) {
|
|
1907
|
+
const source = resolveHashSource(options);
|
|
1908
|
+
const algorithm = normalizeAlgorithm(options.algo ?? "sha256");
|
|
1909
|
+
const actual = digestBuffer(source.content, algorithm);
|
|
1910
|
+
const expected = normalizeExpectedHash(options.expected);
|
|
1911
|
+
return {
|
|
1912
|
+
algorithm,
|
|
1913
|
+
actual,
|
|
1914
|
+
expected,
|
|
1915
|
+
matches: actual === expected,
|
|
1916
|
+
source: source.kind
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
var FIXED_HASH_ALGORITHMS = [
|
|
1920
|
+
"md5",
|
|
1921
|
+
"sha1",
|
|
1922
|
+
"sha256",
|
|
1923
|
+
"sha512"
|
|
1924
|
+
];
|
|
1925
|
+
|
|
1926
|
+
// src/services/id/id.service.ts
|
|
1927
|
+
import crypto2 from "crypto";
|
|
1928
|
+
var LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
|
1929
|
+
var UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
1930
|
+
var DIGITS = "0123456789";
|
|
1931
|
+
var SYMBOLS = "!@#$%^&*()-_=+[]{};:,.?/|~";
|
|
1932
|
+
var CHARSET_MAP = {
|
|
1933
|
+
alnum: `${LOWERCASE}${UPPERCASE}${DIGITS}`,
|
|
1934
|
+
alpha: `${LOWERCASE}${UPPERCASE}`,
|
|
1935
|
+
numeric: DIGITS,
|
|
1936
|
+
hex: "0123456789abcdef",
|
|
1937
|
+
base64url: `${LOWERCASE}${UPPERCASE}${DIGITS}-_`,
|
|
1938
|
+
lower: LOWERCASE,
|
|
1939
|
+
upper: UPPERCASE
|
|
1940
|
+
};
|
|
1941
|
+
function ensurePositiveInteger(value, label) {
|
|
1942
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
1943
|
+
throw new BRUtilsError(`${label} must be a positive integer.`);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
function resolveCount(count) {
|
|
1947
|
+
const resolved = count ?? 1;
|
|
1948
|
+
ensurePositiveInteger(resolved, "Count");
|
|
1949
|
+
return resolved;
|
|
1950
|
+
}
|
|
1951
|
+
function resolveLength(length, fallback) {
|
|
1952
|
+
const resolved = length ?? fallback;
|
|
1953
|
+
ensurePositiveInteger(resolved, "Length");
|
|
1954
|
+
return resolved;
|
|
1955
|
+
}
|
|
1956
|
+
function charsetByName(name) {
|
|
1957
|
+
return CHARSET_MAP[name];
|
|
1958
|
+
}
|
|
1959
|
+
function randomCharacter(charset) {
|
|
1960
|
+
return charset[crypto2.randomInt(0, charset.length)];
|
|
1961
|
+
}
|
|
1962
|
+
function randomString(length, charset) {
|
|
1963
|
+
return Array.from({ length }, () => randomCharacter(charset)).join("");
|
|
1964
|
+
}
|
|
1965
|
+
function ensureNotEmptyCharset(charset) {
|
|
1966
|
+
if (charset.length === 0) {
|
|
1967
|
+
throw new BRUtilsError(
|
|
1968
|
+
"The selected options produced an empty character set."
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
function ensureUppercasePresence(value) {
|
|
1973
|
+
if (/[A-Z]/.test(value)) {
|
|
1974
|
+
return value;
|
|
1975
|
+
}
|
|
1976
|
+
const index = crypto2.randomInt(0, value.length);
|
|
1977
|
+
const replacement = randomCharacter(UPPERCASE);
|
|
1978
|
+
return `${value.slice(0, index)}${replacement}${value.slice(index + 1)}`;
|
|
1979
|
+
}
|
|
1980
|
+
function resolveTokenCharset(charset) {
|
|
1981
|
+
return charsetByName(charset ?? "alnum");
|
|
1982
|
+
}
|
|
1983
|
+
function resolvePasswordCharset(options) {
|
|
1984
|
+
let charset = options.charset ? charsetByName(options.charset) : `${LOWERCASE}${UPPERCASE}${DIGITS}${SYMBOLS}`;
|
|
1985
|
+
if (options.noNumbers) {
|
|
1986
|
+
charset = charset.replace(/[0-9]/g, "");
|
|
1987
|
+
}
|
|
1988
|
+
if (options.noSymbols) {
|
|
1989
|
+
charset = charset.replace(/[!@#$%^&*()_=+[\]{};:,.?/|~-]/g, "");
|
|
1990
|
+
}
|
|
1991
|
+
if (options.uppercase && !/[A-Z]/.test(charset)) {
|
|
1992
|
+
charset += UPPERCASE;
|
|
1993
|
+
}
|
|
1994
|
+
ensureNotEmptyCharset(charset);
|
|
1995
|
+
return charset;
|
|
1996
|
+
}
|
|
1997
|
+
function generateUuidValues(options = {}) {
|
|
1998
|
+
const count = resolveCount(options.count);
|
|
1999
|
+
return Array.from({ length: count }, () => crypto2.randomUUID());
|
|
2000
|
+
}
|
|
2001
|
+
function generateTokenValues(options = {}) {
|
|
2002
|
+
const count = resolveCount(options.count);
|
|
2003
|
+
const length = resolveLength(options.length, 32);
|
|
2004
|
+
const charset = resolveTokenCharset(options.charset);
|
|
2005
|
+
return Array.from({ length: count }, () => randomString(length, charset));
|
|
2006
|
+
}
|
|
2007
|
+
function generatePasswordValues(options = {}) {
|
|
2008
|
+
const count = resolveCount(options.count);
|
|
2009
|
+
const length = resolveLength(options.length, 16);
|
|
2010
|
+
const charset = resolvePasswordCharset(options);
|
|
2011
|
+
return Array.from({ length: count }, () => {
|
|
2012
|
+
let password = randomString(length, charset);
|
|
2013
|
+
if (options.uppercase) {
|
|
2014
|
+
password = ensureUppercasePresence(password);
|
|
2015
|
+
}
|
|
2016
|
+
return password;
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
var CHARSET_NAMES = [
|
|
2020
|
+
"alnum",
|
|
2021
|
+
"alpha",
|
|
2022
|
+
"numeric",
|
|
2023
|
+
"hex",
|
|
2024
|
+
"base64url",
|
|
2025
|
+
"lower",
|
|
2026
|
+
"upper"
|
|
2027
|
+
];
|
|
2028
|
+
|
|
2029
|
+
// src/services/date/date.service.ts
|
|
2030
|
+
var DATE_DIFF_FACTORS = {
|
|
2031
|
+
seconds: 1e3,
|
|
2032
|
+
minutes: 60 * 1e3,
|
|
2033
|
+
hours: 60 * 60 * 1e3,
|
|
2034
|
+
days: 24 * 60 * 60 * 1e3
|
|
2035
|
+
};
|
|
2036
|
+
function ensureValidDate(date, input) {
|
|
2037
|
+
if (Number.isNaN(date.getTime())) {
|
|
2038
|
+
throw new BRUtilsError(`Invalid date value: ${input}`);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
function parseDate(input) {
|
|
2042
|
+
const date = new Date(input);
|
|
2043
|
+
ensureValidDate(date, input);
|
|
2044
|
+
return date;
|
|
2045
|
+
}
|
|
2046
|
+
function ensureValidInteger(value, label) {
|
|
2047
|
+
const resolved = value ?? 0;
|
|
2048
|
+
if (!Number.isInteger(resolved)) {
|
|
2049
|
+
throw new BRUtilsError(`${label} must be an integer.`);
|
|
2050
|
+
}
|
|
2051
|
+
return resolved;
|
|
2052
|
+
}
|
|
2053
|
+
function pad2(value) {
|
|
2054
|
+
return String(value).padStart(2, "0");
|
|
2055
|
+
}
|
|
2056
|
+
function snapshot(date) {
|
|
2057
|
+
const unixMs = date.getTime();
|
|
2058
|
+
return {
|
|
2059
|
+
iso: date.toISOString(),
|
|
2060
|
+
unix: Math.floor(unixMs / 1e3),
|
|
2061
|
+
unixMs
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
function formatUtcDate(date, pattern) {
|
|
2065
|
+
const replacements = {
|
|
2066
|
+
YYYY: String(date.getUTCFullYear()),
|
|
2067
|
+
MM: pad2(date.getUTCMonth() + 1),
|
|
2068
|
+
DD: pad2(date.getUTCDate()),
|
|
2069
|
+
HH: pad2(date.getUTCHours()),
|
|
2070
|
+
mm: pad2(date.getUTCMinutes()),
|
|
2071
|
+
ss: pad2(date.getUTCSeconds())
|
|
2072
|
+
};
|
|
2073
|
+
return pattern.replace(
|
|
2074
|
+
/YYYY|MM|DD|HH|mm|ss/g,
|
|
2075
|
+
(token) => replacements[token]
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
function adjustDate(date, options, multiplier) {
|
|
2079
|
+
const days = ensureValidInteger(options.days, "Days");
|
|
2080
|
+
const hours = ensureValidInteger(options.hours, "Hours");
|
|
2081
|
+
const minutes = ensureValidInteger(options.minutes, "Minutes");
|
|
2082
|
+
const seconds = ensureValidInteger(options.seconds, "Seconds");
|
|
2083
|
+
const totalMs = days * DATE_DIFF_FACTORS.days + hours * DATE_DIFF_FACTORS.hours + minutes * DATE_DIFF_FACTORS.minutes + seconds * DATE_DIFF_FACTORS.seconds;
|
|
2084
|
+
return new Date(date.getTime() + multiplier * totalMs);
|
|
2085
|
+
}
|
|
2086
|
+
function ensureTimeZone(value) {
|
|
2087
|
+
try {
|
|
2088
|
+
new Intl.DateTimeFormat("en-US", { timeZone: value }).format(/* @__PURE__ */ new Date());
|
|
2089
|
+
return value;
|
|
2090
|
+
} catch {
|
|
2091
|
+
throw new BRUtilsError(`Invalid time zone: ${value}`);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
function timeZoneParts(date, timeZone) {
|
|
2095
|
+
const formatter = new Intl.DateTimeFormat("en-CA", {
|
|
2096
|
+
timeZone,
|
|
2097
|
+
year: "numeric",
|
|
2098
|
+
month: "2-digit",
|
|
2099
|
+
day: "2-digit",
|
|
2100
|
+
hour: "2-digit",
|
|
2101
|
+
minute: "2-digit",
|
|
2102
|
+
second: "2-digit",
|
|
2103
|
+
hour12: false
|
|
2104
|
+
});
|
|
2105
|
+
return formatter.formatToParts(date).reduce((acc, part) => {
|
|
2106
|
+
if (part.type !== "literal") {
|
|
2107
|
+
acc[part.type] = part.value;
|
|
2108
|
+
}
|
|
2109
|
+
return acc;
|
|
2110
|
+
}, {});
|
|
2111
|
+
}
|
|
2112
|
+
function currentDateTime() {
|
|
2113
|
+
return snapshot(/* @__PURE__ */ new Date());
|
|
2114
|
+
}
|
|
2115
|
+
function formatDateValue(value, pattern) {
|
|
2116
|
+
return formatUtcDate(parseDate(value), pattern);
|
|
2117
|
+
}
|
|
2118
|
+
function parseDateValue(value) {
|
|
2119
|
+
return {
|
|
2120
|
+
input: value,
|
|
2121
|
+
...snapshot(parseDate(value))
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
function addToDate(value, options) {
|
|
2125
|
+
return adjustDate(parseDate(value), options, 1).toISOString();
|
|
2126
|
+
}
|
|
2127
|
+
function subtractFromDate(value, options) {
|
|
2128
|
+
return adjustDate(parseDate(value), options, -1).toISOString();
|
|
2129
|
+
}
|
|
2130
|
+
function diffDates(from, to, unit = "seconds") {
|
|
2131
|
+
const left = parseDate(from);
|
|
2132
|
+
const right = parseDate(to);
|
|
2133
|
+
const factor = DATE_DIFF_FACTORS[unit];
|
|
2134
|
+
const raw = (right.getTime() - left.getTime()) / factor;
|
|
2135
|
+
return {
|
|
2136
|
+
from,
|
|
2137
|
+
to,
|
|
2138
|
+
unit,
|
|
2139
|
+
value: Number(raw.toFixed(6))
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
function convertDateToUnix(value) {
|
|
2143
|
+
return {
|
|
2144
|
+
input: value,
|
|
2145
|
+
sourceUnit: "milliseconds",
|
|
2146
|
+
...snapshot(parseDate(value))
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
function convertUnixValue(value) {
|
|
2150
|
+
const normalized = String(value).trim();
|
|
2151
|
+
if (!/^-?\d+$/.test(normalized)) {
|
|
2152
|
+
throw new BRUtilsError(`Invalid Unix timestamp value: ${value}`);
|
|
2153
|
+
}
|
|
2154
|
+
const numeric = Number(normalized);
|
|
2155
|
+
const sourceUnit = Math.abs(numeric) >= 1e12 ? "milliseconds" : "seconds";
|
|
2156
|
+
const unixMs = sourceUnit === "seconds" ? numeric * 1e3 : numeric;
|
|
2157
|
+
const date = new Date(unixMs);
|
|
2158
|
+
ensureValidDate(date, normalized);
|
|
2159
|
+
return {
|
|
2160
|
+
input: value,
|
|
2161
|
+
sourceUnit,
|
|
2162
|
+
...snapshot(date)
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2165
|
+
function convertDateToTimeZone(value, timeZone) {
|
|
2166
|
+
const date = parseDate(value);
|
|
2167
|
+
const normalizedTimeZone = ensureTimeZone(timeZone);
|
|
2168
|
+
const parts = timeZoneParts(date, normalizedTimeZone);
|
|
2169
|
+
return {
|
|
2170
|
+
input: value,
|
|
2171
|
+
timeZone: normalizedTimeZone,
|
|
2172
|
+
formatted: `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}:${parts.second}`,
|
|
2173
|
+
iso: date.toISOString()
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
var DATE_DIFF_UNITS = [
|
|
2177
|
+
"seconds",
|
|
2178
|
+
"minutes",
|
|
2179
|
+
"hours",
|
|
2180
|
+
"days"
|
|
2181
|
+
];
|
|
2182
|
+
export {
|
|
2183
|
+
CHARSET_NAMES,
|
|
2184
|
+
CREDIT_CARD_BRAND_CVV_LENGTH,
|
|
2185
|
+
CREDIT_CARD_BRAND_LENGTHS,
|
|
2186
|
+
CREDIT_CARD_BRAND_PREFIXES,
|
|
2187
|
+
DATE_DIFF_UNITS,
|
|
2188
|
+
FIXED_HASH_ALGORITHMS,
|
|
2189
|
+
addToDate,
|
|
2190
|
+
calculateCNPJCheckDigit,
|
|
2191
|
+
calculateCPFCheckDigit,
|
|
2192
|
+
collectZipInputs,
|
|
2193
|
+
compareHash,
|
|
2194
|
+
computeChecksum,
|
|
2195
|
+
computeHash,
|
|
2196
|
+
computeHmac,
|
|
2197
|
+
computeMd5,
|
|
2198
|
+
computeSha1,
|
|
2199
|
+
computeSha256,
|
|
2200
|
+
computeSha512,
|
|
2201
|
+
convertDateToTimeZone,
|
|
2202
|
+
convertDateToUnix,
|
|
2203
|
+
convertJsonToYaml,
|
|
2204
|
+
convertStringCase,
|
|
2205
|
+
convertUnixValue,
|
|
2206
|
+
createUnzipExecutionPlan,
|
|
2207
|
+
createZip,
|
|
2208
|
+
createZipExecutionPlan,
|
|
2209
|
+
currentDateTime,
|
|
2210
|
+
deleteJsonPathValue,
|
|
2211
|
+
detectCreditCardBrand,
|
|
2212
|
+
diffDates,
|
|
2213
|
+
diffJsonValues,
|
|
2214
|
+
ensureCEPLength,
|
|
2215
|
+
ensureCNPJLength,
|
|
2216
|
+
ensureCPFLength,
|
|
2217
|
+
extractText,
|
|
2218
|
+
extractZipFile,
|
|
2219
|
+
flipCoin,
|
|
2220
|
+
formatCEPValue,
|
|
2221
|
+
formatCNPJValue,
|
|
2222
|
+
formatCPFValue,
|
|
2223
|
+
formatDateValue,
|
|
2224
|
+
formatJsonValue,
|
|
2225
|
+
generateCEP,
|
|
2226
|
+
generateCEPBatch,
|
|
2227
|
+
generateCNPJ,
|
|
2228
|
+
generateCNPJBatch,
|
|
2229
|
+
generateCPF,
|
|
2230
|
+
generateCPFBatch,
|
|
2231
|
+
generateCreditCard,
|
|
2232
|
+
generatePasswordValues,
|
|
2233
|
+
generateRandomFloats,
|
|
2234
|
+
generateRandomIntegers,
|
|
2235
|
+
generateRandomNumbers,
|
|
2236
|
+
generateTokenValues,
|
|
2237
|
+
generateUuidValues,
|
|
2238
|
+
getJsonPathValue,
|
|
2239
|
+
getLevenshteinDistance,
|
|
2240
|
+
listUnzip,
|
|
2241
|
+
listZip,
|
|
2242
|
+
maskCEP,
|
|
2243
|
+
maskCNPJ,
|
|
2244
|
+
maskCPF,
|
|
2245
|
+
mergeJsonValues,
|
|
2246
|
+
minifyJsonValue,
|
|
2247
|
+
normalizeCEP,
|
|
2248
|
+
normalizeCNPJ,
|
|
2249
|
+
normalizeCNPJBranch,
|
|
2250
|
+
normalizeCPF,
|
|
2251
|
+
normalizeText,
|
|
2252
|
+
padText,
|
|
2253
|
+
parseDateValue,
|
|
2254
|
+
parseJsonInput,
|
|
2255
|
+
pickRandomItems,
|
|
2256
|
+
pickRandomNumber,
|
|
2257
|
+
removeAccents,
|
|
2258
|
+
replaceText,
|
|
2259
|
+
resolveCEPLeadingDigits,
|
|
2260
|
+
resolveCNPJBranch,
|
|
2261
|
+
resolveCPFRegionDigit,
|
|
2262
|
+
resolveCPFStateFromDigits,
|
|
2263
|
+
resolveUnzipOutputPath,
|
|
2264
|
+
resolveZipOutputPath,
|
|
2265
|
+
rollDice,
|
|
2266
|
+
setJsonPathValue,
|
|
2267
|
+
shuffleRandomItems,
|
|
2268
|
+
slugifyText,
|
|
2269
|
+
stripCEP,
|
|
2270
|
+
stripCNPJ,
|
|
2271
|
+
stripCPF,
|
|
2272
|
+
subtractFromDate,
|
|
2273
|
+
testUnzip,
|
|
2274
|
+
testZip,
|
|
2275
|
+
transformBase64,
|
|
2276
|
+
transformHtmlEntities,
|
|
2277
|
+
transformUrlEncoding,
|
|
2278
|
+
trimText,
|
|
2279
|
+
truncateText,
|
|
2280
|
+
validateCEP,
|
|
2281
|
+
validateCNPJ,
|
|
2282
|
+
validateCPF,
|
|
2283
|
+
validateCreditCard,
|
|
2284
|
+
validateJsonInput
|
|
2285
|
+
};
|
|
2286
|
+
//# sourceMappingURL=index.js.map
|