@danielarndt0/cnpj-db-loader 2.4.0-beta.3 → 2.4.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/cli.js +481 -219
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +317 -296
- package/dist/index.js +360 -214
- package/dist/index.js.map +1 -1
- package/docs/commands.md +6 -0
- package/docs/federal-revenue.md +36 -2
- package/package.json +1 -1
- package/docs/releases/v2.4.0-beta.3.md +0 -42
package/dist/cli.js
CHANGED
|
@@ -100,6 +100,222 @@ function getConfigFilePath() {
|
|
|
100
100
|
return path2.join(os2.homedir(), ".config", "cnpj-db-loader", "config.json");
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// src/services/federal-revenue/client.ts
|
|
104
|
+
var DEFAULT_FEDERAL_REVENUE_WEBDAV_URL = "https://arquivos.receitafederal.gov.br/public.php/webdav";
|
|
105
|
+
var DEFAULT_FEDERAL_REVENUE_USER_AGENT = "cnpj-db-loader federal-revenue-client";
|
|
106
|
+
var REFERENCE_PATTERN = /^\d{4}-\d{2}$/;
|
|
107
|
+
function trimTrailingSlash(value) {
|
|
108
|
+
return value.replace(/\/+$/g, "");
|
|
109
|
+
}
|
|
110
|
+
function normalizeBaseUrl(value) {
|
|
111
|
+
return trimTrailingSlash(value ?? DEFAULT_FEDERAL_REVENUE_WEBDAV_URL);
|
|
112
|
+
}
|
|
113
|
+
function getShareToken(value) {
|
|
114
|
+
const shareToken = value?.trim();
|
|
115
|
+
if (!shareToken) {
|
|
116
|
+
throw new ValidationError(
|
|
117
|
+
"Federal Revenue public share token is not configured. Run `cnpj-db-loader federal-revenue config set share-token <token>` or pass --share-token."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return shareToken;
|
|
121
|
+
}
|
|
122
|
+
function encodePathSegment(value) {
|
|
123
|
+
return encodeURIComponent(value).replace(/%2F/gi, "/");
|
|
124
|
+
}
|
|
125
|
+
function decodeXml(value) {
|
|
126
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
127
|
+
}
|
|
128
|
+
function decodeHrefSegment(value) {
|
|
129
|
+
try {
|
|
130
|
+
return decodeURIComponent(value);
|
|
131
|
+
} catch {
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function getAuthHeader(shareToken) {
|
|
136
|
+
return `Basic ${Buffer.from(`${shareToken}:`).toString("base64")}`;
|
|
137
|
+
}
|
|
138
|
+
function buildUrl(baseUrl, segments = []) {
|
|
139
|
+
if (segments.length === 0) {
|
|
140
|
+
return `${baseUrl}/`;
|
|
141
|
+
}
|
|
142
|
+
return `${baseUrl}/${segments.map(encodePathSegment).join("/")}`;
|
|
143
|
+
}
|
|
144
|
+
function extractFirst(block, tagName) {
|
|
145
|
+
const pattern = new RegExp(
|
|
146
|
+
`<(?:[a-zA-Z0-9_-]+:)?${tagName}\\b[^>]*>([\\s\\S]*?)<\\/(?:[a-zA-Z0-9_-]+:)?${tagName}>`,
|
|
147
|
+
"i"
|
|
148
|
+
);
|
|
149
|
+
const match = block.match(pattern);
|
|
150
|
+
return match?.[1] ? decodeXml(match[1].trim()) : void 0;
|
|
151
|
+
}
|
|
152
|
+
function isCollectionResponse(block) {
|
|
153
|
+
return /<(?:[a-zA-Z0-9_-]+:)?collection\b/i.test(block);
|
|
154
|
+
}
|
|
155
|
+
function getNameFromHref(href) {
|
|
156
|
+
const cleanHref = href.split("?")[0] ?? href;
|
|
157
|
+
const withoutTrailingSlash = cleanHref.replace(/\/+$/g, "");
|
|
158
|
+
const rawName = withoutTrailingSlash.split("/").pop() ?? withoutTrailingSlash;
|
|
159
|
+
return decodeHrefSegment(rawName);
|
|
160
|
+
}
|
|
161
|
+
function parsePropfindXml(xml) {
|
|
162
|
+
const responseBlocks = xml.match(
|
|
163
|
+
/<(?:[a-zA-Z0-9_-]+:)?response\b[\s\S]*?<\/(?:[a-zA-Z0-9_-]+:)?response>/gi
|
|
164
|
+
);
|
|
165
|
+
if (!responseBlocks) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
return responseBlocks.map((block) => {
|
|
169
|
+
const href = extractFirst(block, "href");
|
|
170
|
+
if (!href) {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
const size = extractFirst(block, "getcontentlength");
|
|
174
|
+
const parsedSize = size ? Number.parseInt(size, 10) : void 0;
|
|
175
|
+
const lastModified = extractFirst(block, "getlastmodified");
|
|
176
|
+
const etag = extractFirst(block, "getetag");
|
|
177
|
+
return {
|
|
178
|
+
href,
|
|
179
|
+
name: getNameFromHref(href),
|
|
180
|
+
isCollection: isCollectionResponse(block),
|
|
181
|
+
...Number.isFinite(parsedSize) ? { sizeInBytes: parsedSize } : {},
|
|
182
|
+
...lastModified ? { lastModified } : {},
|
|
183
|
+
...etag ? { etag } : {}
|
|
184
|
+
};
|
|
185
|
+
}).filter((entry) => entry !== void 0);
|
|
186
|
+
}
|
|
187
|
+
async function propfind(pathSegments, options = {}) {
|
|
188
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
189
|
+
const shareToken = getShareToken(options.shareToken);
|
|
190
|
+
let response;
|
|
191
|
+
try {
|
|
192
|
+
response = await fetch(buildUrl(baseUrl, pathSegments), {
|
|
193
|
+
method: "PROPFIND",
|
|
194
|
+
headers: {
|
|
195
|
+
Accept: "application/xml,text/xml,*/*",
|
|
196
|
+
Authorization: getAuthHeader(shareToken),
|
|
197
|
+
Depth: "1",
|
|
198
|
+
"User-Agent": options.userAgent ?? DEFAULT_FEDERAL_REVENUE_USER_AGENT
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
} catch (error) {
|
|
202
|
+
throw new ValidationError(
|
|
203
|
+
`Federal Revenue WebDAV request failed before receiving a response: ${error instanceof Error ? error.message : String(error)}.`,
|
|
204
|
+
{ baseUrl, pathSegments }
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
throw new ValidationError(
|
|
209
|
+
`Federal Revenue WebDAV request failed with status ${response.status} ${response.statusText}.`,
|
|
210
|
+
{ status: response.status, statusText: response.statusText }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
const xml = await response.text();
|
|
214
|
+
return {
|
|
215
|
+
entries: parsePropfindXml(xml),
|
|
216
|
+
baseUrl,
|
|
217
|
+
shareToken
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function validateFederalRevenueReference(reference) {
|
|
221
|
+
if (!REFERENCE_PATTERN.test(reference)) {
|
|
222
|
+
throw new ValidationError(
|
|
223
|
+
`Federal Revenue reference is invalid: ${reference}. Expected YYYY-MM.`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function getCurrentFederalRevenueReference(date = /* @__PURE__ */ new Date()) {
|
|
228
|
+
const year = date.getFullYear();
|
|
229
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
230
|
+
return `${year}-${month}`;
|
|
231
|
+
}
|
|
232
|
+
async function listFederalRevenueReferences(options = {}) {
|
|
233
|
+
const result = await propfind([], options);
|
|
234
|
+
const references = result.entries.filter((entry) => entry.isCollection && REFERENCE_PATTERN.test(entry.name)).map((entry) => ({
|
|
235
|
+
reference: entry.name,
|
|
236
|
+
href: entry.href
|
|
237
|
+
})).sort((left, right) => left.reference.localeCompare(right.reference));
|
|
238
|
+
return {
|
|
239
|
+
references,
|
|
240
|
+
remoteBaseUrl: result.baseUrl
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
async function resolveFederalRevenueReference(input2 = {}) {
|
|
244
|
+
const { references } = await listFederalRevenueReferences(input2);
|
|
245
|
+
const availableReferences = references.map((item) => item.reference);
|
|
246
|
+
const latest = availableReferences.at(-1);
|
|
247
|
+
if (!latest) {
|
|
248
|
+
throw new ValidationError(
|
|
249
|
+
"Federal Revenue reference discovery failed: no monthly references were found in the public share."
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
if (input2.reference) {
|
|
253
|
+
validateFederalRevenueReference(input2.reference);
|
|
254
|
+
if (!availableReferences.includes(input2.reference)) {
|
|
255
|
+
throw new ValidationError(
|
|
256
|
+
`Federal Revenue reference not found: ${input2.reference}. Latest available reference is ${latest}.`,
|
|
257
|
+
{
|
|
258
|
+
requestedReference: input2.reference,
|
|
259
|
+
latestAvailableReference: latest,
|
|
260
|
+
availableReferences
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
mode: "explicit",
|
|
266
|
+
selectedReference: input2.reference,
|
|
267
|
+
availableReferences
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (input2.current) {
|
|
271
|
+
const currentReference = getCurrentFederalRevenueReference();
|
|
272
|
+
if (!availableReferences.includes(currentReference)) {
|
|
273
|
+
throw new ValidationError(
|
|
274
|
+
`Federal Revenue current reference is not available yet: ${currentReference}. Latest available reference is ${latest}.`,
|
|
275
|
+
{
|
|
276
|
+
requestedReference: currentReference,
|
|
277
|
+
latestAvailableReference: latest,
|
|
278
|
+
availableReferences
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
mode: "current",
|
|
284
|
+
selectedReference: currentReference,
|
|
285
|
+
availableReferences
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
mode: "latest",
|
|
290
|
+
selectedReference: latest,
|
|
291
|
+
availableReferences
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function listFederalRevenueFiles(reference, options = {}) {
|
|
295
|
+
validateFederalRevenueReference(reference);
|
|
296
|
+
const result = await propfind([reference], options);
|
|
297
|
+
const files = result.entries.filter(
|
|
298
|
+
(entry) => !entry.isCollection && entry.name.toLowerCase().endsWith(".zip")
|
|
299
|
+
).map((entry) => ({
|
|
300
|
+
name: entry.name,
|
|
301
|
+
href: entry.href,
|
|
302
|
+
downloadUrl: buildUrl(result.baseUrl, [reference, entry.name]),
|
|
303
|
+
...entry.sizeInBytes !== void 0 ? { sizeInBytes: entry.sizeInBytes } : {},
|
|
304
|
+
...entry.lastModified ? { lastModified: entry.lastModified } : {},
|
|
305
|
+
...entry.etag ? { etag: entry.etag } : {}
|
|
306
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
307
|
+
return {
|
|
308
|
+
files,
|
|
309
|
+
remoteBaseUrl: result.baseUrl
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function buildFederalRevenueDownloadHeaders(options = {}) {
|
|
313
|
+
return {
|
|
314
|
+
Authorization: getAuthHeader(getShareToken(options.shareToken)),
|
|
315
|
+
"User-Agent": options.userAgent ?? DEFAULT_FEDERAL_REVENUE_USER_AGENT
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
103
319
|
// src/services/config.service.ts
|
|
104
320
|
async function readDatabaseConfig() {
|
|
105
321
|
const raw = await safeReadText(getConfigFilePath());
|
|
@@ -127,12 +343,149 @@ function assertPostgresUrl(url) {
|
|
|
127
343
|
);
|
|
128
344
|
}
|
|
129
345
|
}
|
|
346
|
+
function assertHttpUrl(url, label) {
|
|
347
|
+
let parsed;
|
|
348
|
+
try {
|
|
349
|
+
parsed = new URL(url);
|
|
350
|
+
} catch {
|
|
351
|
+
throw new ValidationError(`${label} is not a valid URL.`, { url });
|
|
352
|
+
}
|
|
353
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
354
|
+
throw new ValidationError(`${label} must use the http or https protocol.`, {
|
|
355
|
+
url
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function assertNonEmpty(value, label) {
|
|
360
|
+
const trimmed = value.trim();
|
|
361
|
+
if (!trimmed) {
|
|
362
|
+
throw new ValidationError(`${label} cannot be empty.`);
|
|
363
|
+
}
|
|
364
|
+
return trimmed;
|
|
365
|
+
}
|
|
366
|
+
function normalizeFederalRevenueConfigKey(key) {
|
|
367
|
+
const normalized = key.trim().toLowerCase();
|
|
368
|
+
if (["share-token", "share_token", "token"].includes(normalized)) {
|
|
369
|
+
return "share-token";
|
|
370
|
+
}
|
|
371
|
+
if (["webdav-url", "webdav_url", "base-url", "base_url", "url"].includes(
|
|
372
|
+
normalized
|
|
373
|
+
)) {
|
|
374
|
+
return "webdav-url";
|
|
375
|
+
}
|
|
376
|
+
if (["user-agent", "user_agent"].includes(normalized)) {
|
|
377
|
+
return "user-agent";
|
|
378
|
+
}
|
|
379
|
+
throw new ValidationError(
|
|
380
|
+
`Unknown Federal Revenue config key: ${key}. Expected share-token, webdav-url, or user-agent.`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
function assignFederalRevenueConfigValue(config, key, value) {
|
|
384
|
+
if (key === "share-token") {
|
|
385
|
+
return {
|
|
386
|
+
...config,
|
|
387
|
+
shareToken: assertNonEmpty(value, "Federal Revenue share token")
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (key === "webdav-url") {
|
|
391
|
+
const webdavUrl = assertNonEmpty(value, "Federal Revenue WebDAV URL");
|
|
392
|
+
assertHttpUrl(webdavUrl, "Federal Revenue WebDAV URL");
|
|
393
|
+
return { ...config, webdavUrl };
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
...config,
|
|
397
|
+
userAgent: assertNonEmpty(value, "Federal Revenue user agent")
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function deleteFederalRevenueConfigValue(config, key) {
|
|
401
|
+
const nextConfig = { ...config };
|
|
402
|
+
if (key === "share-token") {
|
|
403
|
+
delete nextConfig.shareToken;
|
|
404
|
+
}
|
|
405
|
+
if (key === "webdav-url") {
|
|
406
|
+
delete nextConfig.webdavUrl;
|
|
407
|
+
}
|
|
408
|
+
if (key === "user-agent") {
|
|
409
|
+
delete nextConfig.userAgent;
|
|
410
|
+
}
|
|
411
|
+
return nextConfig;
|
|
412
|
+
}
|
|
413
|
+
function isFederalRevenueConfigEmpty(config) {
|
|
414
|
+
return !config.shareToken && !config.webdavUrl && !config.userAgent;
|
|
415
|
+
}
|
|
130
416
|
async function setDefaultDbUrl(url) {
|
|
131
417
|
assertPostgresUrl(url);
|
|
132
|
-
|
|
418
|
+
const currentConfig = await readDatabaseConfig();
|
|
419
|
+
await writeDatabaseConfig({ ...currentConfig, defaultDbUrl: url });
|
|
133
420
|
}
|
|
134
421
|
async function resetDefaultDbUrl() {
|
|
135
|
-
await
|
|
422
|
+
const currentConfig = await readDatabaseConfig();
|
|
423
|
+
const nextConfig = { ...currentConfig };
|
|
424
|
+
delete nextConfig.defaultDbUrl;
|
|
425
|
+
await writeDatabaseConfig(nextConfig);
|
|
426
|
+
}
|
|
427
|
+
async function setFederalRevenueConfigValue(key, value) {
|
|
428
|
+
const normalizedKey = normalizeFederalRevenueConfigKey(key);
|
|
429
|
+
const currentConfig = await readDatabaseConfig();
|
|
430
|
+
const federalRevenueConfig = assignFederalRevenueConfigValue(
|
|
431
|
+
currentConfig.federalRevenue ?? {},
|
|
432
|
+
normalizedKey,
|
|
433
|
+
value
|
|
434
|
+
);
|
|
435
|
+
await writeDatabaseConfig({
|
|
436
|
+
...currentConfig,
|
|
437
|
+
federalRevenue: federalRevenueConfig
|
|
438
|
+
});
|
|
439
|
+
return getFederalRevenueEffectiveConfig(federalRevenueConfig);
|
|
440
|
+
}
|
|
441
|
+
async function resetFederalRevenueConfig(key) {
|
|
442
|
+
const currentConfig = await readDatabaseConfig();
|
|
443
|
+
if (!key) {
|
|
444
|
+
const nextConfig2 = { ...currentConfig };
|
|
445
|
+
delete nextConfig2.federalRevenue;
|
|
446
|
+
await writeDatabaseConfig(nextConfig2);
|
|
447
|
+
return getFederalRevenueEffectiveConfig({});
|
|
448
|
+
}
|
|
449
|
+
const normalizedKey = normalizeFederalRevenueConfigKey(key);
|
|
450
|
+
const federalRevenueConfig = deleteFederalRevenueConfigValue(
|
|
451
|
+
currentConfig.federalRevenue ?? {},
|
|
452
|
+
normalizedKey
|
|
453
|
+
);
|
|
454
|
+
const nextConfig = { ...currentConfig };
|
|
455
|
+
if (isFederalRevenueConfigEmpty(federalRevenueConfig)) {
|
|
456
|
+
delete nextConfig.federalRevenue;
|
|
457
|
+
} else {
|
|
458
|
+
nextConfig.federalRevenue = federalRevenueConfig;
|
|
459
|
+
}
|
|
460
|
+
await writeDatabaseConfig(nextConfig);
|
|
461
|
+
return getFederalRevenueEffectiveConfig(federalRevenueConfig);
|
|
462
|
+
}
|
|
463
|
+
function getFederalRevenueEffectiveConfig(config = {}) {
|
|
464
|
+
return {
|
|
465
|
+
webdavUrl: config.webdavUrl ?? DEFAULT_FEDERAL_REVENUE_WEBDAV_URL,
|
|
466
|
+
userAgent: config.userAgent ?? DEFAULT_FEDERAL_REVENUE_USER_AGENT,
|
|
467
|
+
...config.shareToken ? { shareToken: config.shareToken } : {},
|
|
468
|
+
configured: {
|
|
469
|
+
webdavUrl: Boolean(config.webdavUrl),
|
|
470
|
+
userAgent: Boolean(config.userAgent),
|
|
471
|
+
shareToken: Boolean(config.shareToken)
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
async function readFederalRevenueEffectiveConfig() {
|
|
476
|
+
const currentConfig = await readDatabaseConfig();
|
|
477
|
+
return getFederalRevenueEffectiveConfig(currentConfig.federalRevenue ?? {});
|
|
478
|
+
}
|
|
479
|
+
async function resolveFederalRevenueClientOptions(overrides = {}) {
|
|
480
|
+
const currentConfig = await readDatabaseConfig();
|
|
481
|
+
const effectiveConfig = getFederalRevenueEffectiveConfig(
|
|
482
|
+
currentConfig.federalRevenue ?? {}
|
|
483
|
+
);
|
|
484
|
+
return {
|
|
485
|
+
baseUrl: overrides.baseUrl ?? effectiveConfig.webdavUrl,
|
|
486
|
+
shareToken: overrides.shareToken ?? effectiveConfig.shareToken,
|
|
487
|
+
userAgent: overrides.userAgent ?? effectiveConfig.userAgent
|
|
488
|
+
};
|
|
136
489
|
}
|
|
137
490
|
|
|
138
491
|
// src/services/database.service.ts
|
|
@@ -6685,217 +7038,6 @@ async function showQuarantineRow(id, options) {
|
|
|
6685
7038
|
return record;
|
|
6686
7039
|
}
|
|
6687
7040
|
|
|
6688
|
-
// src/services/federal-revenue/client.ts
|
|
6689
|
-
var DEFAULT_FEDERAL_REVENUE_SHARE_TOKEN = "YggdBLfdninEJX9";
|
|
6690
|
-
var DEFAULT_FEDERAL_REVENUE_WEBDAV_URL = "https://arquivos.receitafederal.gov.br/public.php/webdav";
|
|
6691
|
-
var DEFAULT_FEDERAL_REVENUE_USER_AGENT = "cnpj-db-loader federal-revenue-client";
|
|
6692
|
-
var REFERENCE_PATTERN = /^\d{4}-\d{2}$/;
|
|
6693
|
-
function trimTrailingSlash(value) {
|
|
6694
|
-
return value.replace(/\/+$/g, "");
|
|
6695
|
-
}
|
|
6696
|
-
function normalizeBaseUrl(value) {
|
|
6697
|
-
return trimTrailingSlash(value ?? DEFAULT_FEDERAL_REVENUE_WEBDAV_URL);
|
|
6698
|
-
}
|
|
6699
|
-
function getShareToken(value) {
|
|
6700
|
-
return value ?? DEFAULT_FEDERAL_REVENUE_SHARE_TOKEN;
|
|
6701
|
-
}
|
|
6702
|
-
function encodePathSegment(value) {
|
|
6703
|
-
return encodeURIComponent(value).replace(/%2F/gi, "/");
|
|
6704
|
-
}
|
|
6705
|
-
function decodeXml(value) {
|
|
6706
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
6707
|
-
}
|
|
6708
|
-
function decodeHrefSegment(value) {
|
|
6709
|
-
try {
|
|
6710
|
-
return decodeURIComponent(value);
|
|
6711
|
-
} catch {
|
|
6712
|
-
return value;
|
|
6713
|
-
}
|
|
6714
|
-
}
|
|
6715
|
-
function getAuthHeader(shareToken) {
|
|
6716
|
-
return `Basic ${Buffer.from(`${shareToken}:`).toString("base64")}`;
|
|
6717
|
-
}
|
|
6718
|
-
function buildUrl(baseUrl, segments = []) {
|
|
6719
|
-
if (segments.length === 0) {
|
|
6720
|
-
return `${baseUrl}/`;
|
|
6721
|
-
}
|
|
6722
|
-
return `${baseUrl}/${segments.map(encodePathSegment).join("/")}`;
|
|
6723
|
-
}
|
|
6724
|
-
function extractFirst(block, tagName) {
|
|
6725
|
-
const pattern = new RegExp(
|
|
6726
|
-
`<(?:[a-zA-Z0-9_-]+:)?${tagName}\\b[^>]*>([\\s\\S]*?)<\\/(?:[a-zA-Z0-9_-]+:)?${tagName}>`,
|
|
6727
|
-
"i"
|
|
6728
|
-
);
|
|
6729
|
-
const match = block.match(pattern);
|
|
6730
|
-
return match?.[1] ? decodeXml(match[1].trim()) : void 0;
|
|
6731
|
-
}
|
|
6732
|
-
function isCollectionResponse(block) {
|
|
6733
|
-
return /<(?:[a-zA-Z0-9_-]+:)?collection\b/i.test(block);
|
|
6734
|
-
}
|
|
6735
|
-
function getNameFromHref(href) {
|
|
6736
|
-
const cleanHref = href.split("?")[0] ?? href;
|
|
6737
|
-
const withoutTrailingSlash = cleanHref.replace(/\/+$/g, "");
|
|
6738
|
-
const rawName = withoutTrailingSlash.split("/").pop() ?? withoutTrailingSlash;
|
|
6739
|
-
return decodeHrefSegment(rawName);
|
|
6740
|
-
}
|
|
6741
|
-
function parsePropfindXml(xml) {
|
|
6742
|
-
const responseBlocks = xml.match(
|
|
6743
|
-
/<(?:[a-zA-Z0-9_-]+:)?response\b[\s\S]*?<\/(?:[a-zA-Z0-9_-]+:)?response>/gi
|
|
6744
|
-
);
|
|
6745
|
-
if (!responseBlocks) {
|
|
6746
|
-
return [];
|
|
6747
|
-
}
|
|
6748
|
-
return responseBlocks.map((block) => {
|
|
6749
|
-
const href = extractFirst(block, "href");
|
|
6750
|
-
if (!href) {
|
|
6751
|
-
return void 0;
|
|
6752
|
-
}
|
|
6753
|
-
const size = extractFirst(block, "getcontentlength");
|
|
6754
|
-
const parsedSize = size ? Number.parseInt(size, 10) : void 0;
|
|
6755
|
-
const lastModified = extractFirst(block, "getlastmodified");
|
|
6756
|
-
const etag = extractFirst(block, "getetag");
|
|
6757
|
-
return {
|
|
6758
|
-
href,
|
|
6759
|
-
name: getNameFromHref(href),
|
|
6760
|
-
isCollection: isCollectionResponse(block),
|
|
6761
|
-
...Number.isFinite(parsedSize) ? { sizeInBytes: parsedSize } : {},
|
|
6762
|
-
...lastModified ? { lastModified } : {},
|
|
6763
|
-
...etag ? { etag } : {}
|
|
6764
|
-
};
|
|
6765
|
-
}).filter((entry) => entry !== void 0);
|
|
6766
|
-
}
|
|
6767
|
-
async function propfind(pathSegments, options = {}) {
|
|
6768
|
-
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
6769
|
-
const shareToken = getShareToken(options.shareToken);
|
|
6770
|
-
let response;
|
|
6771
|
-
try {
|
|
6772
|
-
response = await fetch(buildUrl(baseUrl, pathSegments), {
|
|
6773
|
-
method: "PROPFIND",
|
|
6774
|
-
headers: {
|
|
6775
|
-
Accept: "application/xml,text/xml,*/*",
|
|
6776
|
-
Authorization: getAuthHeader(shareToken),
|
|
6777
|
-
Depth: "1",
|
|
6778
|
-
"User-Agent": options.userAgent ?? DEFAULT_FEDERAL_REVENUE_USER_AGENT
|
|
6779
|
-
}
|
|
6780
|
-
});
|
|
6781
|
-
} catch (error) {
|
|
6782
|
-
throw new ValidationError(
|
|
6783
|
-
`Federal Revenue WebDAV request failed before receiving a response: ${error instanceof Error ? error.message : String(error)}.`,
|
|
6784
|
-
{ baseUrl, pathSegments }
|
|
6785
|
-
);
|
|
6786
|
-
}
|
|
6787
|
-
if (!response.ok) {
|
|
6788
|
-
throw new ValidationError(
|
|
6789
|
-
`Federal Revenue WebDAV request failed with status ${response.status} ${response.statusText}.`,
|
|
6790
|
-
{ status: response.status, statusText: response.statusText }
|
|
6791
|
-
);
|
|
6792
|
-
}
|
|
6793
|
-
const xml = await response.text();
|
|
6794
|
-
return {
|
|
6795
|
-
entries: parsePropfindXml(xml),
|
|
6796
|
-
baseUrl,
|
|
6797
|
-
shareToken
|
|
6798
|
-
};
|
|
6799
|
-
}
|
|
6800
|
-
function validateFederalRevenueReference(reference) {
|
|
6801
|
-
if (!REFERENCE_PATTERN.test(reference)) {
|
|
6802
|
-
throw new ValidationError(
|
|
6803
|
-
`Federal Revenue reference is invalid: ${reference}. Expected YYYY-MM.`
|
|
6804
|
-
);
|
|
6805
|
-
}
|
|
6806
|
-
}
|
|
6807
|
-
function getCurrentFederalRevenueReference(date = /* @__PURE__ */ new Date()) {
|
|
6808
|
-
const year = date.getFullYear();
|
|
6809
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
6810
|
-
return `${year}-${month}`;
|
|
6811
|
-
}
|
|
6812
|
-
async function listFederalRevenueReferences(options = {}) {
|
|
6813
|
-
const result = await propfind([], options);
|
|
6814
|
-
const references = result.entries.filter((entry) => entry.isCollection && REFERENCE_PATTERN.test(entry.name)).map((entry) => ({
|
|
6815
|
-
reference: entry.name,
|
|
6816
|
-
href: entry.href
|
|
6817
|
-
})).sort((left, right) => left.reference.localeCompare(right.reference));
|
|
6818
|
-
return {
|
|
6819
|
-
references,
|
|
6820
|
-
remoteBaseUrl: result.baseUrl
|
|
6821
|
-
};
|
|
6822
|
-
}
|
|
6823
|
-
async function resolveFederalRevenueReference(input2 = {}) {
|
|
6824
|
-
const { references } = await listFederalRevenueReferences(input2);
|
|
6825
|
-
const availableReferences = references.map((item) => item.reference);
|
|
6826
|
-
const latest = availableReferences.at(-1);
|
|
6827
|
-
if (!latest) {
|
|
6828
|
-
throw new ValidationError(
|
|
6829
|
-
"Federal Revenue reference discovery failed: no monthly references were found in the public share."
|
|
6830
|
-
);
|
|
6831
|
-
}
|
|
6832
|
-
if (input2.reference) {
|
|
6833
|
-
validateFederalRevenueReference(input2.reference);
|
|
6834
|
-
if (!availableReferences.includes(input2.reference)) {
|
|
6835
|
-
throw new ValidationError(
|
|
6836
|
-
`Federal Revenue reference not found: ${input2.reference}. Latest available reference is ${latest}.`,
|
|
6837
|
-
{
|
|
6838
|
-
requestedReference: input2.reference,
|
|
6839
|
-
latestAvailableReference: latest,
|
|
6840
|
-
availableReferences
|
|
6841
|
-
}
|
|
6842
|
-
);
|
|
6843
|
-
}
|
|
6844
|
-
return {
|
|
6845
|
-
mode: "explicit",
|
|
6846
|
-
selectedReference: input2.reference,
|
|
6847
|
-
availableReferences
|
|
6848
|
-
};
|
|
6849
|
-
}
|
|
6850
|
-
if (input2.current) {
|
|
6851
|
-
const currentReference = getCurrentFederalRevenueReference();
|
|
6852
|
-
if (!availableReferences.includes(currentReference)) {
|
|
6853
|
-
throw new ValidationError(
|
|
6854
|
-
`Federal Revenue current reference is not available yet: ${currentReference}. Latest available reference is ${latest}.`,
|
|
6855
|
-
{
|
|
6856
|
-
requestedReference: currentReference,
|
|
6857
|
-
latestAvailableReference: latest,
|
|
6858
|
-
availableReferences
|
|
6859
|
-
}
|
|
6860
|
-
);
|
|
6861
|
-
}
|
|
6862
|
-
return {
|
|
6863
|
-
mode: "current",
|
|
6864
|
-
selectedReference: currentReference,
|
|
6865
|
-
availableReferences
|
|
6866
|
-
};
|
|
6867
|
-
}
|
|
6868
|
-
return {
|
|
6869
|
-
mode: "latest",
|
|
6870
|
-
selectedReference: latest,
|
|
6871
|
-
availableReferences
|
|
6872
|
-
};
|
|
6873
|
-
}
|
|
6874
|
-
async function listFederalRevenueFiles(reference, options = {}) {
|
|
6875
|
-
validateFederalRevenueReference(reference);
|
|
6876
|
-
const result = await propfind([reference], options);
|
|
6877
|
-
const files = result.entries.filter(
|
|
6878
|
-
(entry) => !entry.isCollection && entry.name.toLowerCase().endsWith(".zip")
|
|
6879
|
-
).map((entry) => ({
|
|
6880
|
-
name: entry.name,
|
|
6881
|
-
href: entry.href,
|
|
6882
|
-
downloadUrl: buildUrl(result.baseUrl, [reference, entry.name]),
|
|
6883
|
-
...entry.sizeInBytes !== void 0 ? { sizeInBytes: entry.sizeInBytes } : {},
|
|
6884
|
-
...entry.lastModified ? { lastModified: entry.lastModified } : {},
|
|
6885
|
-
...entry.etag ? { etag: entry.etag } : {}
|
|
6886
|
-
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
6887
|
-
return {
|
|
6888
|
-
files,
|
|
6889
|
-
remoteBaseUrl: result.baseUrl
|
|
6890
|
-
};
|
|
6891
|
-
}
|
|
6892
|
-
function buildFederalRevenueDownloadHeaders(options = {}) {
|
|
6893
|
-
return {
|
|
6894
|
-
Authorization: getAuthHeader(getShareToken(options.shareToken)),
|
|
6895
|
-
"User-Agent": options.userAgent ?? DEFAULT_FEDERAL_REVENUE_USER_AGENT
|
|
6896
|
-
};
|
|
6897
|
-
}
|
|
6898
|
-
|
|
6899
7041
|
// src/services/federal-revenue/download.ts
|
|
6900
7042
|
import { createWriteStream } from "fs";
|
|
6901
7043
|
import { mkdir as mkdir5, rename, stat as stat5, unlink } from "fs/promises";
|
|
@@ -9852,6 +9994,28 @@ function printDatabaseConfigSummary(config, logFilePath) {
|
|
|
9852
9994
|
);
|
|
9853
9995
|
console.log(`${theme.muted("Log file:")} ${resolveLogFilePath(logFilePath)}`);
|
|
9854
9996
|
}
|
|
9997
|
+
function printFederalRevenueConfigSummary(config, logFilePath) {
|
|
9998
|
+
console.log(
|
|
9999
|
+
theme.successLabel("FEDERAL REVENUE"),
|
|
10000
|
+
"Federal Revenue configuration loaded."
|
|
10001
|
+
);
|
|
10002
|
+
console.log(
|
|
10003
|
+
formatKeyValue(
|
|
10004
|
+
"WebDAV URL",
|
|
10005
|
+
`${config.webdavUrl}${config.configured.webdavUrl ? "" : " (default)"}`
|
|
10006
|
+
)
|
|
10007
|
+
);
|
|
10008
|
+
console.log(
|
|
10009
|
+
formatKeyValue(
|
|
10010
|
+
"User agent",
|
|
10011
|
+
`${config.userAgent}${config.configured.userAgent ? "" : " (default)"}`
|
|
10012
|
+
)
|
|
10013
|
+
);
|
|
10014
|
+
console.log(
|
|
10015
|
+
formatKeyValue("Share token", config.shareToken ?? "not configured")
|
|
10016
|
+
);
|
|
10017
|
+
console.log(`${theme.muted("Log file:")} ${resolveLogFilePath(logFilePath)}`);
|
|
10018
|
+
}
|
|
9855
10019
|
function printDatabaseCleanupSummary(summary, logFilePath) {
|
|
9856
10020
|
console.log(
|
|
9857
10021
|
theme.successLabel("DATABASE"),
|
|
@@ -11377,8 +11541,21 @@ function applySharedOptions(options, target) {
|
|
|
11377
11541
|
if (options.shareToken) {
|
|
11378
11542
|
target.shareToken = options.shareToken;
|
|
11379
11543
|
}
|
|
11544
|
+
if (options.userAgent) {
|
|
11545
|
+
target.userAgent = options.userAgent;
|
|
11546
|
+
}
|
|
11380
11547
|
return target;
|
|
11381
11548
|
}
|
|
11549
|
+
async function resolveSharedOptions(referenceArgument, options) {
|
|
11550
|
+
const mergedOptions = mergeSharedOptions(referenceArgument, options);
|
|
11551
|
+
const clientOptions = await resolveFederalRevenueClientOptions(mergedOptions);
|
|
11552
|
+
return {
|
|
11553
|
+
...mergedOptions,
|
|
11554
|
+
...clientOptions.baseUrl ? { baseUrl: clientOptions.baseUrl } : {},
|
|
11555
|
+
...clientOptions.shareToken ? { shareToken: clientOptions.shareToken } : {},
|
|
11556
|
+
...clientOptions.userAgent ? { userAgent: clientOptions.userAgent } : {}
|
|
11557
|
+
};
|
|
11558
|
+
}
|
|
11382
11559
|
function buildDownloadOptions(options) {
|
|
11383
11560
|
const downloadOptions = applySharedOptions(
|
|
11384
11561
|
options,
|
|
@@ -11463,6 +11640,9 @@ function registerSharedOptions(command) {
|
|
|
11463
11640
|
).option(
|
|
11464
11641
|
"--share-token <token>",
|
|
11465
11642
|
"Override the public Federal Revenue share token."
|
|
11643
|
+
).option(
|
|
11644
|
+
"--user-agent <value>",
|
|
11645
|
+
"Override the Federal Revenue HTTP user agent."
|
|
11466
11646
|
);
|
|
11467
11647
|
}
|
|
11468
11648
|
function registerDownloadOptions(command) {
|
|
@@ -11497,6 +11677,70 @@ function registerFederalRevenueCommands(program) {
|
|
|
11497
11677
|
const federalRevenue = program.command("federal-revenue").alias("revenue").description(
|
|
11498
11678
|
"Check, download, sync, and maintain CNPJ monthly files from the Federal Revenue public share."
|
|
11499
11679
|
);
|
|
11680
|
+
const config = federalRevenue.command("config").description(
|
|
11681
|
+
"Read, persist, test, or reset Federal Revenue public share settings."
|
|
11682
|
+
);
|
|
11683
|
+
config.command("set").argument(
|
|
11684
|
+
"<key>",
|
|
11685
|
+
"Configuration key: share-token, webdav-url, or user-agent."
|
|
11686
|
+
).argument("<value>", "Configuration value to persist.").description(
|
|
11687
|
+
"Persist a Federal Revenue setting in the local CNPJ DB Loader config file."
|
|
11688
|
+
).action(async (key, value) => {
|
|
11689
|
+
const effectiveConfig = await setFederalRevenueConfigValue(key, value);
|
|
11690
|
+
const logFilePath = await writeCommandLog("federal-revenue-config-set", {
|
|
11691
|
+
key,
|
|
11692
|
+
effectiveConfig
|
|
11693
|
+
});
|
|
11694
|
+
printFederalRevenueConfigSummary(effectiveConfig, logFilePath);
|
|
11695
|
+
});
|
|
11696
|
+
config.command("show").description("Show the currently persisted Federal Revenue configuration.").action(async () => {
|
|
11697
|
+
const effectiveConfig = await readFederalRevenueEffectiveConfig();
|
|
11698
|
+
const logFilePath = await writeCommandLog(
|
|
11699
|
+
"federal-revenue-config-show",
|
|
11700
|
+
effectiveConfig
|
|
11701
|
+
);
|
|
11702
|
+
printFederalRevenueConfigSummary(effectiveConfig, logFilePath);
|
|
11703
|
+
});
|
|
11704
|
+
config.command("test").description("Test the configured Federal Revenue WebDAV connection.").action(async () => {
|
|
11705
|
+
const clientOptions = await resolveFederalRevenueClientOptions();
|
|
11706
|
+
const result = await listFederalRevenueReferences(clientOptions);
|
|
11707
|
+
const references = result.references.map((item) => item.reference);
|
|
11708
|
+
const latestReference = references.at(-1) ?? "not found";
|
|
11709
|
+
const logFilePath = await writeCommandLog("federal-revenue-config-test", {
|
|
11710
|
+
remoteBaseUrl: result.remoteBaseUrl,
|
|
11711
|
+
referencesFound: references.length,
|
|
11712
|
+
latestReference
|
|
11713
|
+
});
|
|
11714
|
+
printFederalRevenueConfigSummary(
|
|
11715
|
+
await readFederalRevenueEffectiveConfig(),
|
|
11716
|
+
logFilePath
|
|
11717
|
+
);
|
|
11718
|
+
console.log(
|
|
11719
|
+
`Federal Revenue WebDAV connection succeeded. References found: ${references.length}. Latest reference: ${latestReference}.`
|
|
11720
|
+
);
|
|
11721
|
+
});
|
|
11722
|
+
config.command("reset").argument(
|
|
11723
|
+
"[key]",
|
|
11724
|
+
"Optional key to reset: share-token, webdav-url, or user-agent. When omitted, all Federal Revenue settings are reset."
|
|
11725
|
+
).option("-f, --force", "Skip the confirmation prompt.").description(
|
|
11726
|
+
"Reset one Federal Revenue setting or all persisted Federal Revenue settings."
|
|
11727
|
+
).action(async (key, options) => {
|
|
11728
|
+
const target = key ? `Federal Revenue ${key}` : "all Federal Revenue";
|
|
11729
|
+
const confirmed = await confirmFederalRevenueAction(
|
|
11730
|
+
`Reset ${target} configuration?`,
|
|
11731
|
+
options.force
|
|
11732
|
+
);
|
|
11733
|
+
if (!confirmed) {
|
|
11734
|
+
console.log("Federal Revenue config reset cancelled.");
|
|
11735
|
+
return;
|
|
11736
|
+
}
|
|
11737
|
+
const effectiveConfig = await resetFederalRevenueConfig(key);
|
|
11738
|
+
const logFilePath = await writeCommandLog(
|
|
11739
|
+
"federal-revenue-config-reset",
|
|
11740
|
+
{ key: key ?? "all", effectiveConfig }
|
|
11741
|
+
);
|
|
11742
|
+
printFederalRevenueConfigSummary(effectiveConfig, logFilePath);
|
|
11743
|
+
});
|
|
11500
11744
|
registerSharedOptions(
|
|
11501
11745
|
federalRevenue.command("check").argument(
|
|
11502
11746
|
"[reference]",
|
|
@@ -11506,7 +11750,10 @@ function registerFederalRevenueCommands(program) {
|
|
|
11506
11750
|
)
|
|
11507
11751
|
).action(
|
|
11508
11752
|
async (referenceArgument, options) => {
|
|
11509
|
-
const resolvedOptions =
|
|
11753
|
+
const resolvedOptions = await resolveSharedOptions(
|
|
11754
|
+
referenceArgument,
|
|
11755
|
+
options
|
|
11756
|
+
);
|
|
11510
11757
|
const summary = await checkFederalRevenueDataset(
|
|
11511
11758
|
applySharedOptions(resolvedOptions, {})
|
|
11512
11759
|
);
|
|
@@ -11526,7 +11773,10 @@ function registerFederalRevenueCommands(program) {
|
|
|
11526
11773
|
)
|
|
11527
11774
|
).action(
|
|
11528
11775
|
async (referenceArgument, options) => {
|
|
11529
|
-
const resolvedOptions =
|
|
11776
|
+
const resolvedOptions = await resolveSharedOptions(
|
|
11777
|
+
referenceArgument,
|
|
11778
|
+
options
|
|
11779
|
+
);
|
|
11530
11780
|
const confirmed = await confirmFederalRevenueAction(
|
|
11531
11781
|
"Download Federal Revenue CNPJ ZIP files now? Existing completed files are skipped unless --overwrite is used.",
|
|
11532
11782
|
options.force
|
|
@@ -11559,7 +11809,10 @@ function registerFederalRevenueCommands(program) {
|
|
|
11559
11809
|
)
|
|
11560
11810
|
).action(
|
|
11561
11811
|
async (referenceArgument, options) => {
|
|
11562
|
-
const resolvedOptions =
|
|
11812
|
+
const resolvedOptions = await resolveSharedOptions(
|
|
11813
|
+
referenceArgument,
|
|
11814
|
+
options
|
|
11815
|
+
);
|
|
11563
11816
|
const summary = await getFederalRevenueStatus(
|
|
11564
11817
|
buildStatusOptions({ ...options, ...resolvedOptions })
|
|
11565
11818
|
);
|
|
@@ -11582,7 +11835,10 @@ function registerFederalRevenueCommands(program) {
|
|
|
11582
11835
|
)
|
|
11583
11836
|
).action(
|
|
11584
11837
|
async (referenceArgument, options) => {
|
|
11585
|
-
const resolvedOptions =
|
|
11838
|
+
const resolvedOptions = await resolveSharedOptions(
|
|
11839
|
+
referenceArgument,
|
|
11840
|
+
options
|
|
11841
|
+
);
|
|
11586
11842
|
const confirmed = await confirmFederalRevenueAction(
|
|
11587
11843
|
"Retry incomplete Federal Revenue files now? Completed files are kept.",
|
|
11588
11844
|
options.force
|
|
@@ -11615,7 +11871,10 @@ function registerFederalRevenueCommands(program) {
|
|
|
11615
11871
|
)
|
|
11616
11872
|
).action(
|
|
11617
11873
|
async (referenceArgument, options) => {
|
|
11618
|
-
const resolvedOptions =
|
|
11874
|
+
const resolvedOptions = await resolveSharedOptions(
|
|
11875
|
+
referenceArgument,
|
|
11876
|
+
options
|
|
11877
|
+
);
|
|
11619
11878
|
const actionLabel = options.all ? "remove the entire selected Federal Revenue reference folder" : options.failed ? "remove failed and partial Federal Revenue files" : "remove Federal Revenue .part files";
|
|
11620
11879
|
const confirmed = await confirmFederalRevenueAction(
|
|
11621
11880
|
`This will ${actionLabel}. Continue?`,
|
|
@@ -11670,7 +11929,10 @@ function registerFederalRevenueCommands(program) {
|
|
|
11670
11929
|
)
|
|
11671
11930
|
).action(
|
|
11672
11931
|
async (referenceArgument, options) => {
|
|
11673
|
-
const resolvedOptions =
|
|
11932
|
+
const resolvedOptions = await resolveSharedOptions(
|
|
11933
|
+
referenceArgument,
|
|
11934
|
+
options
|
|
11935
|
+
);
|
|
11674
11936
|
const confirmed = await confirmFederalRevenueAction(
|
|
11675
11937
|
"Run the full Federal Revenue sync now? This downloads files, extracts archives, sanitizes the dataset, and imports it into PostgreSQL.",
|
|
11676
11938
|
options.force
|