@fulmenhq/tsfulmen 0.3.3 → 0.4.1

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.
Files changed (123) hide show
  1. package/CHANGELOG.md +76 -1
  2. package/README.md +30 -1
  3. package/config/crucible-ts/taxonomy/metrics.yaml +1 -1
  4. package/dist/appidentity/index.js +9 -2209
  5. package/dist/appidentity/index.js.map +1 -1
  6. package/dist/assets/index.d.ts +184 -0
  7. package/dist/assets/index.js +4 -0
  8. package/dist/assets/index.js.map +1 -0
  9. package/dist/bin/prometheus-cli.js +15 -9139
  10. package/dist/bin/prometheus-cli.js.map +1 -1
  11. package/dist/bin/schema-cli.js +4 -6098
  12. package/dist/bin/schema-cli.js.map +1 -1
  13. package/dist/bin/signals-cli.js +15 -1910
  14. package/dist/bin/signals-cli.js.map +1 -1
  15. package/dist/chunk-24BAF63H.js +194 -0
  16. package/dist/chunk-24BAF63H.js.map +1 -0
  17. package/dist/chunk-3HISR2XI.js +36 -0
  18. package/dist/chunk-3HISR2XI.js.map +1 -0
  19. package/dist/chunk-56F2EEAG.js +328 -0
  20. package/dist/chunk-56F2EEAG.js.map +1 -0
  21. package/dist/chunk-5HZEJGZ5.js +725 -0
  22. package/dist/chunk-5HZEJGZ5.js.map +1 -0
  23. package/dist/chunk-7YWACZEJ.js +837 -0
  24. package/dist/chunk-7YWACZEJ.js.map +1 -0
  25. package/dist/chunk-BCO3FUUO.js +682 -0
  26. package/dist/chunk-BCO3FUUO.js.map +1 -0
  27. package/dist/chunk-GOTEQYAD.js +410 -0
  28. package/dist/chunk-GOTEQYAD.js.map +1 -0
  29. package/dist/chunk-HY6JQOFN.js +32 -0
  30. package/dist/chunk-HY6JQOFN.js.map +1 -0
  31. package/dist/chunk-LMNAUTW7.js +43 -0
  32. package/dist/chunk-LMNAUTW7.js.map +1 -0
  33. package/dist/chunk-LWAH2UG7.js +103 -0
  34. package/dist/chunk-LWAH2UG7.js.map +1 -0
  35. package/dist/chunk-OHE2AOH4.js +273 -0
  36. package/dist/chunk-OHE2AOH4.js.map +1 -0
  37. package/dist/chunk-P3DNOZOG.js +1488 -0
  38. package/dist/chunk-P3DNOZOG.js.map +1 -0
  39. package/dist/chunk-PQ6TKPUN.js +27 -0
  40. package/dist/chunk-PQ6TKPUN.js.map +1 -0
  41. package/dist/chunk-QCCN6POA.js +211 -0
  42. package/dist/chunk-QCCN6POA.js.map +1 -0
  43. package/dist/chunk-QGTNV4U2.js +985 -0
  44. package/dist/chunk-QGTNV4U2.js.map +1 -0
  45. package/dist/chunk-RSLFR4TO.js +631 -0
  46. package/dist/chunk-RSLFR4TO.js.map +1 -0
  47. package/dist/chunk-SNV5WG5V.js +9412 -0
  48. package/dist/chunk-SNV5WG5V.js.map +1 -0
  49. package/dist/chunk-UP2VWCW5.js +33 -0
  50. package/dist/chunk-UP2VWCW5.js.map +1 -0
  51. package/dist/chunk-VJIGX7MH.js +208 -0
  52. package/dist/chunk-VJIGX7MH.js.map +1 -0
  53. package/dist/chunk-WCCMRVZF.js +130 -0
  54. package/dist/chunk-WCCMRVZF.js.map +1 -0
  55. package/dist/chunk-WSMVLWLB.js +269 -0
  56. package/dist/chunk-WSMVLWLB.js.map +1 -0
  57. package/dist/config/index.js +6 -1220
  58. package/dist/config/index.js.map +1 -1
  59. package/dist/crucible/fulpack/index.js +2 -25
  60. package/dist/crucible/fulpack/index.js.map +1 -1
  61. package/dist/crucible/index.js +13 -1129
  62. package/dist/crucible/index.js.map +1 -1
  63. package/dist/docscribe/index.js +2 -680
  64. package/dist/docscribe/index.js.map +1 -1
  65. package/dist/errors/index.js +5 -1831
  66. package/dist/errors/index.js.map +1 -1
  67. package/dist/errors-D7KXV3QW.js +4 -0
  68. package/dist/errors-D7KXV3QW.js.map +1 -0
  69. package/dist/errors-R7ZODJXS.js +8 -0
  70. package/dist/errors-R7ZODJXS.js.map +1 -0
  71. package/dist/export-EA3RNQNS.js +6 -0
  72. package/dist/export-EA3RNQNS.js.map +1 -0
  73. package/dist/foundry/index.js +340 -3949
  74. package/dist/foundry/index.js.map +1 -1
  75. package/dist/foundry/similarity/index.js +3 -134
  76. package/dist/foundry/similarity/index.js.map +1 -1
  77. package/dist/fulencode/index.js +3 -319
  78. package/dist/fulencode/index.js.map +1 -1
  79. package/dist/fulhash/index.js +3 -611
  80. package/dist/fulhash/index.js.map +1 -1
  81. package/dist/fulpack/index.js +3 -1515
  82. package/dist/fulpack/index.js.map +1 -1
  83. package/dist/hash-YOJYVOWH.js +4 -0
  84. package/dist/hash-YOJYVOWH.js.map +1 -0
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.js +14 -4395
  87. package/dist/index.js.map +1 -1
  88. package/dist/loader-VP2ZIC4O.js +9 -0
  89. package/dist/loader-VP2ZIC4O.js.map +1 -0
  90. package/dist/logging/index.js +2 -723
  91. package/dist/logging/index.js.map +1 -1
  92. package/dist/pathfinder/index.js +18 -2341
  93. package/dist/pathfinder/index.js.map +1 -1
  94. package/dist/prom-client-PNEBGD3G.js +4801 -0
  95. package/dist/prom-client-PNEBGD3G.js.map +1 -0
  96. package/dist/reports/license-inventory.csv +1 -1
  97. package/dist/schema/index.d.ts +19 -6
  98. package/dist/schema/index.js +4 -6148
  99. package/dist/schema/index.js.map +1 -1
  100. package/dist/signals/index.js +8 -2767
  101. package/dist/signals/index.js.map +1 -1
  102. package/dist/similarity/index.js +3 -134
  103. package/dist/similarity/index.js.map +1 -1
  104. package/dist/telemetry/http/index.js +9 -930
  105. package/dist/telemetry/http/index.js.map +1 -1
  106. package/dist/telemetry/index.js +4 -1561
  107. package/dist/telemetry/index.js.map +1 -1
  108. package/dist/telemetry/prometheus/index.js +7 -8422
  109. package/dist/telemetry/prometheus/index.js.map +1 -1
  110. package/dist/validator-I3ASDVDB.js +6 -0
  111. package/dist/validator-I3ASDVDB.js.map +1 -0
  112. package/package.json +5 -1
  113. package/schemas/crucible-ts/library/module-manifest/v1.0.0/module-manifest.schema.json +1 -1
  114. package/schemas/crucible-ts/observability/logging/v1.0.0/definitions.schema.json +1 -1
  115. package/schemas/crucible-ts/observability/logging/v1.0.0/log-event.schema.json +13 -13
  116. package/schemas/crucible-ts/observability/logging/v1.0.0/logger-config.schema.json +2 -2
  117. package/schemas/crucible-ts/observability/logging/v1.0.0/logging-policy.schema.json +1 -1
  118. package/schemas/crucible-ts/observability/logging/v1.0.0/middleware-config.schema.json +2 -2
  119. package/schemas/crucible-ts/observability/logging/v1.0.0/severity-filter.schema.json +3 -3
  120. package/schemas/crucible-ts/upstream/3leaps/crucible/PROVENANCE.md +3 -3
  121. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/catalog/classifiers/README.md +1 -1
  122. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/classifiers-framework.md +1 -1
  123. package/schemas/crucible-ts/upstream/3leaps/crucible/docs/standards/data-sensitivity-classification.md +1 -1
@@ -1,1517 +1,5 @@
1
- import { existsSync, mkdirSync, statSync, createReadStream, createWriteStream, readdirSync, lstatSync } from 'fs';
2
- import { dirname, basename, join } from 'path';
3
- import { pipeline } from 'stream';
4
- import { createGunzip, createGzip } from 'zlib';
5
- import { TarArchive, ZipArchive } from 'archiver';
6
-
7
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
- }) : x)(function(x) {
10
- if (typeof require !== "undefined") return require.apply(this, arguments);
11
- throw Error('Dynamic require of "' + x + '" is not supported');
12
- });
13
-
14
- // src/crucible/fulpack/types.ts
15
- var ArchiveFormat = /* @__PURE__ */ ((ArchiveFormat2) => {
16
- ArchiveFormat2["TAR"] = "tar";
17
- ArchiveFormat2["TAR_GZ"] = "tar.gz";
18
- ArchiveFormat2["ZIP"] = "zip";
19
- ArchiveFormat2["GZIP"] = "gzip";
20
- return ArchiveFormat2;
21
- })(ArchiveFormat || {});
22
- var EntryType = /* @__PURE__ */ ((EntryType2) => {
23
- EntryType2["FILE"] = "file";
24
- EntryType2["DIRECTORY"] = "directory";
25
- EntryType2["SYMLINK"] = "symlink";
26
- return EntryType2;
27
- })(EntryType || {});
28
- var Operation = /* @__PURE__ */ ((Operation2) => {
29
- Operation2["CREATE"] = "create";
30
- Operation2["EXTRACT"] = "extract";
31
- Operation2["SCAN"] = "scan";
32
- Operation2["VERIFY"] = "verify";
33
- Operation2["INFO"] = "info";
34
- return Operation2;
35
- })(Operation || {});
36
-
37
- // src/fulpack/errors.ts
38
- function createFulpackError(code, message, operation, context) {
39
- return {
40
- code,
41
- message,
42
- operation,
43
- ...context?.path && { path: context.path },
44
- ...context?.archive && { archive: context.archive },
45
- ...context?.details && { details: context.details }
46
- };
47
- }
48
- var ERROR_CODES = {
49
- // Validation Errors
50
- INVALID_ARCHIVE_FORMAT: "INVALID_ARCHIVE_FORMAT",
51
- INVALID_PATH: "INVALID_PATH",
52
- INVALID_OPTIONS: "INVALID_OPTIONS",
53
- // Security Errors
54
- PATH_TRAVERSAL: "PATH_TRAVERSAL",
55
- ABSOLUTE_PATH: "ABSOLUTE_PATH",
56
- SYMLINK_ESCAPE: "SYMLINK_ESCAPE",
57
- DECOMPRESSION_BOMB: "DECOMPRESSION_BOMB",
58
- CHECKSUM_MISMATCH: "CHECKSUM_MISMATCH",
59
- // Runtime Errors
60
- ARCHIVE_NOT_FOUND: "ARCHIVE_NOT_FOUND",
61
- ARCHIVE_CORRUPT: "ARCHIVE_CORRUPT",
62
- EXTRACTION_FAILED: "EXTRACTION_FAILED",
63
- PERMISSION_DENIED: "PERMISSION_DENIED",
64
- DISK_FULL: "DISK_FULL",
65
- SOURCE_NOT_FOUND: "SOURCE_NOT_FOUND"
66
- };
67
- var FulpackOperationError = class extends Error {
68
- code;
69
- operation;
70
- path;
71
- archive;
72
- details;
73
- constructor(error) {
74
- super(error.message);
75
- this.name = "FulpackOperationError";
76
- this.code = error.code;
77
- this.operation = error.operation;
78
- this.path = error.path;
79
- this.archive = error.archive;
80
- this.details = error.details;
81
- }
82
- /**
83
- * Convert back to canonical error format
84
- */
85
- toCanonical() {
86
- return {
87
- code: this.code,
88
- message: this.message,
89
- operation: this.operation,
90
- ...this.path && { path: this.path },
91
- ...this.archive && { archive: this.archive },
92
- ...this.details && { details: this.details }
93
- };
94
- }
95
- };
96
- function hasPathTraversal(path) {
97
- return path.includes("..") || path.includes("\\..");
98
- }
99
- function isAbsolutePath(path) {
100
- return path.startsWith("/") || /^[a-zA-Z]:/.test(path);
101
- }
102
- function validatePath(path, operation, allowAbsolute = false) {
103
- if (!allowAbsolute && isAbsolutePath(path)) {
104
- return createFulpackError(
105
- ERROR_CODES.ABSOLUTE_PATH,
106
- `Absolute path not allowed: ${path}`,
107
- operation,
108
- { path }
109
- );
110
- }
111
- if (hasPathTraversal(path)) {
112
- return createFulpackError(
113
- ERROR_CODES.PATH_TRAVERSAL,
114
- `Path traversal detected: ${path}`,
115
- operation,
116
- { path }
117
- );
118
- }
119
- return null;
120
- }
121
- function checkDecompressionBomb(uncompressedSize, compressedSize, maxSize, maxEntries, currentEntries) {
122
- if (uncompressedSize > maxSize) {
123
- return createFulpackError(
124
- ERROR_CODES.DECOMPRESSION_BOMB,
125
- `Archive exceeds maximum size limit`,
126
- "extract" /* EXTRACT */,
127
- {
128
- details: {
129
- actual_size: uncompressedSize,
130
- max_size: maxSize
131
- }
132
- }
133
- );
134
- }
135
- if (currentEntries > maxEntries) {
136
- return createFulpackError(
137
- ERROR_CODES.DECOMPRESSION_BOMB,
138
- `Archive exceeds maximum entry limit`,
139
- "extract" /* EXTRACT */,
140
- {
141
- details: {
142
- actual_size: currentEntries,
143
- max_size: maxEntries
144
- }
145
- }
146
- );
147
- }
148
- if (compressedSize > 0 && uncompressedSize / compressedSize > 100) {
149
- return createFulpackError(
150
- ERROR_CODES.DECOMPRESSION_BOMB,
151
- `Suspicious compression ratio detected`,
152
- "extract" /* EXTRACT */,
153
- {
154
- details: {
155
- compression_ratio: uncompressedSize / compressedSize
156
- }
157
- }
158
- );
159
- }
160
- return null;
161
- }
162
-
163
- // src/fulpack/core.ts
164
- var DEFAULTS = {
165
- CREATE: {
166
- compression_level: 6,
167
- checksum_algorithm: "sha256",
168
- preserve_permissions: true,
169
- follow_symlinks: false
170
- },
171
- EXTRACT: {
172
- overwrite: "error",
173
- verify_checksums: true,
174
- preserve_permissions: true,
175
- max_size: 1024 * 1024 * 1024,
176
- // 1GB
177
- max_entries: 1e5
178
- },
179
- SCAN: {
180
- include_metadata: true,
181
- max_entries: 1e5
182
- }
183
- };
184
- async function create(source, output, format, options) {
185
- const opts = { ...DEFAULTS.CREATE, ...options };
186
- const sources = Array.isArray(source) ? source : [source];
187
- if (format !== "tar" /* TAR */ && format !== "tar.gz" /* TAR_GZ */ && format !== "zip" /* ZIP */ && format !== "gzip" /* GZIP */) {
188
- throw new FulpackOperationError(
189
- createFulpackError(
190
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
191
- `Format ${format} not yet supported`,
192
- "create" /* CREATE */
193
- )
194
- );
195
- }
196
- const outputDir = dirname(output);
197
- if (!existsSync(outputDir)) {
198
- mkdirSync(outputDir, { recursive: true });
199
- }
200
- if (format === "zip" /* ZIP */) {
201
- return await createZipArchive(sources, output, opts);
202
- } else if (format === "tar.gz" /* TAR_GZ */) {
203
- return await createTarGzArchive(sources, output, opts);
204
- } else if (format === "tar" /* TAR */) {
205
- return await createTarArchive(sources, output, opts);
206
- } else if (format === "gzip" /* GZIP */) {
207
- return await createGzipFile(sources, output, opts);
208
- } else {
209
- throw new FulpackOperationError(
210
- createFulpackError(
211
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
212
- `Unsupported format for creation: ${format}`,
213
- "create" /* CREATE */
214
- )
215
- );
216
- }
217
- }
218
- async function extract(archive, destination, options) {
219
- const opts = { ...DEFAULTS.EXTRACT, ...options };
220
- if (!existsSync(archive)) {
221
- throw new FulpackOperationError(
222
- createFulpackError(
223
- ERROR_CODES.ARCHIVE_NOT_FOUND,
224
- `Archive not found: ${archive}`,
225
- "extract" /* EXTRACT */,
226
- { archive }
227
- )
228
- );
229
- }
230
- if (!existsSync(destination)) {
231
- mkdirSync(destination, { recursive: true });
232
- }
233
- if (archive.endsWith(".tar.gz") || archive.endsWith(".tgz")) {
234
- return await extractTarGz(archive, destination, opts);
235
- } else if (archive.endsWith(".tar")) {
236
- return await extractTar(archive, destination, opts);
237
- } else if (archive.endsWith(".zip")) {
238
- return await extractZip(archive, destination, opts);
239
- } else if (archive.endsWith(".gz")) {
240
- return await extractGzipFile(archive, destination, opts);
241
- } else {
242
- throw new FulpackOperationError(
243
- createFulpackError(
244
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
245
- `Unsupported archive format for extraction: ${archive}`,
246
- "extract" /* EXTRACT */,
247
- { archive }
248
- )
249
- );
250
- }
251
- }
252
- async function scan(archive, options) {
253
- const opts = { ...DEFAULTS.SCAN, ...options };
254
- if (!existsSync(archive)) {
255
- throw new FulpackOperationError(
256
- createFulpackError(
257
- ERROR_CODES.ARCHIVE_NOT_FOUND,
258
- `Archive not found: ${archive}`,
259
- "scan" /* SCAN */,
260
- { archive }
261
- )
262
- );
263
- }
264
- const filename = basename(archive);
265
- if (filename.endsWith(".tar.gz") || filename.endsWith(".tgz")) {
266
- return await scanTarGz(archive, opts);
267
- } else if (filename.endsWith(".tar")) {
268
- return await scanTar(archive, opts);
269
- } else if (filename.endsWith(".zip")) {
270
- return await scanZip(archive, opts);
271
- } else if (filename.endsWith(".gz")) {
272
- return await scanGzipFile(archive, opts);
273
- } else {
274
- throw new FulpackOperationError(
275
- createFulpackError(
276
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
277
- `Unsupported archive format: ${filename}`,
278
- "scan" /* SCAN */,
279
- { archive }
280
- )
281
- );
282
- }
283
- }
284
- async function verify(archive, _options) {
285
- if (!existsSync(archive)) {
286
- throw new FulpackOperationError(
287
- createFulpackError(
288
- ERROR_CODES.ARCHIVE_NOT_FOUND,
289
- `Archive not found: ${archive}`,
290
- "verify" /* VERIFY */,
291
- { archive }
292
- )
293
- );
294
- }
295
- const stats = statSync(archive);
296
- const errors = [];
297
- const warnings = [];
298
- const checks_performed = [];
299
- let entry_count = 0;
300
- let checksums_verified = 0;
301
- checks_performed.push("structure_valid");
302
- if (stats.size === 0) {
303
- errors.push(
304
- createFulpackError(ERROR_CODES.ARCHIVE_CORRUPT, "Archive file is empty", "verify" /* VERIFY */, {
305
- archive
306
- })
307
- );
308
- return {
309
- valid: false,
310
- errors,
311
- warnings,
312
- entry_count: 0,
313
- checksums_verified: 0,
314
- checks_performed
315
- };
316
- }
317
- let entries;
318
- try {
319
- entries = await scan(archive, { include_metadata: true });
320
- entry_count = entries.length;
321
- } catch (error) {
322
- errors.push(
323
- createFulpackError(
324
- ERROR_CODES.ARCHIVE_CORRUPT,
325
- `Failed to scan archive: ${error instanceof Error ? error.message : String(error)}`,
326
- "verify" /* VERIFY */,
327
- { archive, details: { original_error: error } }
328
- )
329
- );
330
- return {
331
- valid: false,
332
- errors,
333
- warnings,
334
- entry_count: 0,
335
- checksums_verified: 0,
336
- checks_performed
337
- };
338
- }
339
- checks_performed.push("no_path_traversal");
340
- for (const entry of entries) {
341
- const pathError = validatePath(entry.path, "verify" /* VERIFY */, true);
342
- if (pathError) {
343
- errors.push({ ...pathError, archive });
344
- }
345
- }
346
- const symlinks = entries.filter((e) => e.type === "symlink");
347
- if (symlinks.length > 0) {
348
- checks_performed.push("symlinks_safe");
349
- for (const symlink of symlinks) {
350
- if (symlink.symlink_target) {
351
- const targetError = validatePath(symlink.symlink_target, "verify" /* VERIFY */, true);
352
- if (targetError) {
353
- errors.push(
354
- createFulpackError(
355
- ERROR_CODES.ARCHIVE_CORRUPT,
356
- `Symlink '${symlink.path}' targets unsafe location: ${symlink.symlink_target}`,
357
- "verify" /* VERIFY */,
358
- { archive, path: symlink.path, details: { symlink_target: symlink.symlink_target } }
359
- )
360
- );
361
- }
362
- }
363
- }
364
- }
365
- checks_performed.push("no_decompression_bomb");
366
- const total_size = entries.reduce((sum, entry) => sum + (entry.size || 0), 0);
367
- const compressed_size = stats.size;
368
- const compression_ratio = total_size > 0 && compressed_size > 0 ? total_size / compressed_size : 1;
369
- if (compression_ratio > 100) {
370
- warnings.push(
371
- `High compression ratio detected: ${compression_ratio.toFixed(1)}:1 (threshold: 100:1)`
372
- );
373
- }
374
- const MAX_SIZE = 1024 * 1024 * 1024;
375
- if (total_size > MAX_SIZE) {
376
- warnings.push(
377
- `Total uncompressed size (${(total_size / (1024 * 1024)).toFixed(1)}MB) exceeds recommended limit (1GB)`
378
- );
379
- }
380
- const MAX_ENTRIES = 1e5;
381
- if (entry_count > MAX_ENTRIES) {
382
- warnings.push(`Entry count (${entry_count}) exceeds recommended limit (${MAX_ENTRIES})`);
383
- }
384
- const entriesWithChecksums = entries.filter((e) => e.checksum !== void 0);
385
- if (entriesWithChecksums.length > 0) {
386
- checks_performed.push("checksums_verified");
387
- checksums_verified = entriesWithChecksums.length;
388
- } else if (entry_count > 0) {
389
- warnings.push("Archive does not contain checksums for integrity verification");
390
- }
391
- return {
392
- valid: errors.length === 0,
393
- errors,
394
- warnings,
395
- entry_count,
396
- checksums_verified,
397
- checks_performed
398
- };
399
- }
400
- async function info(archive) {
401
- if (!existsSync(archive)) {
402
- throw new FulpackOperationError(
403
- createFulpackError(
404
- ERROR_CODES.ARCHIVE_NOT_FOUND,
405
- `Archive not found: ${archive}`,
406
- "info" /* INFO */,
407
- { archive }
408
- )
409
- );
410
- }
411
- const stats = statSync(archive);
412
- const filename = basename(archive);
413
- let format;
414
- let compression = "none";
415
- if (filename.endsWith(".tar.gz") || filename.endsWith(".tgz")) {
416
- format = "tar.gz" /* TAR_GZ */;
417
- compression = "gzip";
418
- } else if (filename.endsWith(".zip")) {
419
- format = "zip" /* ZIP */;
420
- compression = "deflate";
421
- } else if (filename.endsWith(".gz")) {
422
- format = "gzip" /* GZIP */;
423
- compression = "gzip";
424
- } else if (filename.endsWith(".tar")) {
425
- format = "tar" /* TAR */;
426
- compression = "none";
427
- } else {
428
- throw new FulpackOperationError(
429
- createFulpackError(
430
- ERROR_CODES.INVALID_ARCHIVE_FORMAT,
431
- `Unsupported archive format: ${filename}`,
432
- "info" /* INFO */,
433
- { archive }
434
- )
435
- );
436
- }
437
- const entries = await scan(archive, { include_metadata: true });
438
- const entry_count = entries.length;
439
- const total_size = entries.reduce((sum, entry) => sum + (entry.size || 0), 0);
440
- const compressed_size = stats.size;
441
- const compression_ratio = total_size > 0 ? compressed_size / total_size : 1;
442
- const has_checksums = entries.some((entry) => entry.checksum !== void 0);
443
- return {
444
- format,
445
- compression,
446
- entry_count,
447
- total_size,
448
- compressed_size,
449
- compression_ratio,
450
- has_checksums,
451
- created: stats.mtime.toISOString()
452
- };
453
- }
454
- async function extractGzipFile(archive, destination, options) {
455
- const filename = basename(archive, ".gz");
456
- const outputPath = join(destination, filename);
457
- if (existsSync(outputPath)) {
458
- if (options.overwrite === "error") {
459
- throw new FulpackOperationError(
460
- createFulpackError(
461
- ERROR_CODES.EXTRACTION_FAILED,
462
- `Output file already exists: ${outputPath}`,
463
- "extract" /* EXTRACT */,
464
- { path: outputPath, archive }
465
- )
466
- );
467
- } else if (options.overwrite === "skip") {
468
- return {
469
- extracted_count: 0,
470
- skipped_count: 1,
471
- error_count: 0,
472
- errors: [],
473
- warnings: [`Skipped existing file: ${outputPath}`]
474
- };
475
- }
476
- }
477
- try {
478
- const readStream = createReadStream(archive);
479
- const writeStream = createWriteStream(outputPath);
480
- const gunzip = createGunzip();
481
- await new Promise((resolve, reject) => {
482
- pipeline(readStream, gunzip, writeStream, (error) => {
483
- if (error) {
484
- reject(error);
485
- } else {
486
- resolve(void 0);
487
- }
488
- });
489
- });
490
- return {
491
- extracted_count: 1,
492
- skipped_count: 0,
493
- error_count: 0,
494
- errors: [],
495
- warnings: []
496
- };
497
- } catch (error) {
498
- throw new FulpackOperationError(
499
- createFulpackError(
500
- ERROR_CODES.EXTRACTION_FAILED,
501
- `Failed to extract gzip file: ${error instanceof Error ? error.message : "Unknown error"}`,
502
- "extract" /* EXTRACT */,
503
- { archive, details: { original_error: error } }
504
- )
505
- );
506
- }
507
- }
508
- async function scanGzipFile(archive, options) {
509
- const filename = basename(archive, ".gz");
510
- const stats = statSync(archive);
511
- const entry = {
512
- path: filename,
513
- type: "file" /* FILE */,
514
- size: 0,
515
- // Would need to decompress to get actual size
516
- compressed_size: stats.size,
517
- modified: stats.mtime.toISOString(),
518
- ...options.include_metadata && { mode: "0644" }
519
- // Default for gzip files
520
- };
521
- return [entry];
522
- }
523
- async function createTarGzArchive(sources, output, options) {
524
- const writeStream = createWriteStream(output);
525
- const archive = new TarArchive({
526
- gzip: true,
527
- gzipOptions: {
528
- level: options.compression_level
529
- }
530
- });
531
- let entryCount = 0;
532
- let totalSize = 0;
533
- const writePromise = new Promise((resolve, reject) => {
534
- writeStream.on("close", () => resolve());
535
- writeStream.on("error", reject);
536
- archive.on(
537
- "error",
538
- (error) => reject(
539
- new FulpackOperationError(
540
- createFulpackError(
541
- ERROR_CODES.EXTRACTION_FAILED,
542
- `TAR.GZ creation failed: ${error.message}`,
543
- "create" /* CREATE */,
544
- { details: { original_error: error } }
545
- )
546
- )
547
- )
548
- );
549
- });
550
- archive.pipe(writeStream);
551
- for (const sourcePath of sources) {
552
- if (!existsSync(sourcePath)) {
553
- throw new FulpackOperationError(
554
- createFulpackError(
555
- ERROR_CODES.SOURCE_NOT_FOUND,
556
- `Source not found: ${sourcePath}`,
557
- "create" /* CREATE */,
558
- { details: { source: sourcePath } }
559
- )
560
- );
561
- }
562
- const stats = statSync(sourcePath);
563
- if (stats.isDirectory()) {
564
- await addDirectoryToTarGzArchive(archive, sourcePath, "", options);
565
- } else {
566
- const entryName = basename(sourcePath);
567
- archive.file(sourcePath, {
568
- name: entryName,
569
- mode: options.preserve_permissions ? stats.mode : 420
570
- });
571
- entryCount++;
572
- totalSize += stats.size;
573
- }
574
- }
575
- archive.finalize().catch(() => {
576
- });
577
- await writePromise;
578
- const outputStats = statSync(output);
579
- return {
580
- format: "tar.gz" /* TAR_GZ */,
581
- compression: "gzip",
582
- entry_count: entryCount,
583
- total_size: totalSize,
584
- compressed_size: outputStats.size,
585
- compression_ratio: totalSize > 0 ? outputStats.size / totalSize : 0,
586
- has_checksums: false,
587
- // TODO: Add fulhash checksum integration
588
- created: outputStats.mtime.toISOString()
589
- };
590
- }
591
- async function createZipArchive(sources, output, options) {
592
- const writeStream = createWriteStream(output);
593
- const archive = new ZipArchive({
594
- zlib: { level: options.compression_level }
595
- });
596
- let entryCount = 0;
597
- let totalSize = 0;
598
- const writePromise = new Promise((resolve, reject) => {
599
- writeStream.on("close", () => resolve());
600
- writeStream.on("error", reject);
601
- archive.on(
602
- "error",
603
- (error) => reject(
604
- new FulpackOperationError(
605
- createFulpackError(
606
- ERROR_CODES.EXTRACTION_FAILED,
607
- `ZIP creation failed: ${error.message}`,
608
- "create" /* CREATE */,
609
- { details: { original_error: error } }
610
- )
611
- )
612
- )
613
- );
614
- });
615
- archive.pipe(writeStream);
616
- for (const sourcePath of sources) {
617
- if (!existsSync(sourcePath)) {
618
- throw new FulpackOperationError(
619
- createFulpackError(
620
- ERROR_CODES.SOURCE_NOT_FOUND,
621
- `Source not found: ${sourcePath}`,
622
- "create" /* CREATE */,
623
- { details: { source: sourcePath } }
624
- )
625
- );
626
- }
627
- const stats = statSync(sourcePath);
628
- if (stats.isDirectory()) {
629
- await addDirectoryToZipArchive(archive, sourcePath, "", options);
630
- } else {
631
- const entryName = basename(sourcePath);
632
- archive.file(sourcePath, {
633
- name: entryName,
634
- mode: options.preserve_permissions ? stats.mode : 420
635
- });
636
- entryCount++;
637
- totalSize += stats.size;
638
- }
639
- }
640
- archive.finalize().catch(() => {
641
- });
642
- await writePromise;
643
- const outputStats = statSync(output);
644
- return {
645
- format: "zip" /* ZIP */,
646
- compression: "deflate",
647
- entry_count: entryCount,
648
- total_size: totalSize,
649
- compressed_size: outputStats.size,
650
- compression_ratio: totalSize > 0 ? outputStats.size / totalSize : 0,
651
- has_checksums: false,
652
- created: outputStats.mtime.toISOString()
653
- };
654
- }
655
- async function addDirectoryToTarGzArchive(archive, dirPath, archivePrefix, options) {
656
- const entries = readdirSync(dirPath);
657
- for (const entry of entries) {
658
- const fullPath = join(dirPath, entry);
659
- const archivePath = join(archivePrefix, entry);
660
- const stats = lstatSync(fullPath);
661
- if (stats.isSymbolicLink()) {
662
- if (!options.follow_symlinks) {
663
- continue;
664
- }
665
- const targetStats = statSync(fullPath);
666
- if (targetStats.isDirectory()) {
667
- await addDirectoryToTarGzArchive(archive, fullPath, archivePath, options);
668
- } else {
669
- archive.file(fullPath, {
670
- name: archivePath,
671
- mode: options.preserve_permissions ? targetStats.mode : 420
672
- });
673
- }
674
- } else if (stats.isDirectory()) {
675
- await addDirectoryToTarGzArchive(archive, fullPath, archivePath, options);
676
- } else {
677
- archive.file(fullPath, {
678
- name: archivePath,
679
- mode: options.preserve_permissions ? stats.mode : 420
680
- });
681
- }
682
- }
683
- }
684
- async function createTarArchive(sources, output, options) {
685
- const writeStream = createWriteStream(output);
686
- const archive = new TarArchive({
687
- gzip: false
688
- // Uncompressed
689
- });
690
- let entryCount = 0;
691
- let totalSize = 0;
692
- const writePromise = new Promise((resolve, reject) => {
693
- writeStream.on("close", () => resolve());
694
- writeStream.on("error", reject);
695
- archive.on(
696
- "error",
697
- (error) => reject(
698
- new FulpackOperationError(
699
- createFulpackError(
700
- ERROR_CODES.EXTRACTION_FAILED,
701
- `TAR creation failed: ${error.message}`,
702
- "create" /* CREATE */,
703
- { details: { original_error: error } }
704
- )
705
- )
706
- )
707
- );
708
- });
709
- archive.pipe(writeStream);
710
- for (const sourcePath of sources) {
711
- if (!existsSync(sourcePath)) {
712
- throw new FulpackOperationError(
713
- createFulpackError(
714
- ERROR_CODES.SOURCE_NOT_FOUND,
715
- `Source not found: ${sourcePath}`,
716
- "create" /* CREATE */,
717
- { details: { source: sourcePath } }
718
- )
719
- );
720
- }
721
- const stats = statSync(sourcePath);
722
- if (stats.isDirectory()) {
723
- await addDirectoryToTarArchive(archive, sourcePath, "", options);
724
- } else {
725
- const entryName = basename(sourcePath);
726
- archive.file(sourcePath, {
727
- name: entryName,
728
- mode: options.preserve_permissions ? stats.mode : 420
729
- });
730
- entryCount++;
731
- totalSize += stats.size;
732
- }
733
- }
734
- archive.finalize().catch(() => {
735
- });
736
- await writePromise;
737
- const outputStats = statSync(output);
738
- return {
739
- format: "tar" /* TAR */,
740
- compression: "none",
741
- entry_count: entryCount,
742
- total_size: totalSize,
743
- compressed_size: outputStats.size,
744
- compression_ratio: 1,
745
- // Uncompressed
746
- has_checksums: false,
747
- // TODO: Add fulhash checksum integration
748
- created: outputStats.mtime.toISOString()
749
- };
750
- }
751
- async function addDirectoryToTarArchive(archive, dirPath, archivePrefix, options) {
752
- const entries = readdirSync(dirPath);
753
- for (const entry of entries) {
754
- const fullPath = join(dirPath, entry);
755
- const archivePath = join(archivePrefix, entry);
756
- const stats = lstatSync(fullPath);
757
- if (stats.isSymbolicLink()) {
758
- if (!options.follow_symlinks) {
759
- continue;
760
- }
761
- const targetStats = statSync(fullPath);
762
- if (targetStats.isDirectory()) {
763
- await addDirectoryToTarArchive(archive, fullPath, archivePath, options);
764
- } else {
765
- archive.file(fullPath, {
766
- name: archivePath,
767
- mode: options.preserve_permissions ? targetStats.mode : 420
768
- });
769
- }
770
- } else if (stats.isDirectory()) {
771
- await addDirectoryToTarArchive(archive, fullPath, archivePath, options);
772
- } else {
773
- archive.file(fullPath, {
774
- name: archivePath,
775
- mode: options.preserve_permissions ? stats.mode : 420
776
- });
777
- }
778
- }
779
- }
780
- async function createGzipFile(sources, output, options) {
781
- if (sources.length !== 1) {
782
- throw new FulpackOperationError(
783
- createFulpackError(
784
- ERROR_CODES.INVALID_OPTIONS,
785
- `GZIP format requires exactly one source file, got ${sources.length}`,
786
- "create" /* CREATE */
787
- )
788
- );
789
- }
790
- const sourcePath = sources[0];
791
- if (!existsSync(sourcePath)) {
792
- throw new FulpackOperationError(
793
- createFulpackError(
794
- ERROR_CODES.SOURCE_NOT_FOUND,
795
- `Source not found: ${sourcePath}`,
796
- "create" /* CREATE */,
797
- { details: { source: sourcePath } }
798
- )
799
- );
800
- }
801
- const stats = statSync(sourcePath);
802
- if (stats.isDirectory()) {
803
- throw new FulpackOperationError(
804
- createFulpackError(
805
- ERROR_CODES.INVALID_OPTIONS,
806
- "GZIP format does not support directories",
807
- "create" /* CREATE */,
808
- { details: { source: sourcePath } }
809
- )
810
- );
811
- }
812
- const readStream = createReadStream(sourcePath);
813
- const writeStream = createWriteStream(output);
814
- const gzip = createGzip({ level: options.compression_level });
815
- await new Promise((resolve, reject) => {
816
- pipeline(readStream, gzip, writeStream, (error) => {
817
- if (error) {
818
- reject(error);
819
- } else {
820
- resolve(void 0);
821
- }
822
- });
823
- });
824
- const outputStats = statSync(output);
825
- return {
826
- format: "gzip" /* GZIP */,
827
- compression: "gzip",
828
- entry_count: 1,
829
- total_size: stats.size,
830
- compressed_size: outputStats.size,
831
- compression_ratio: stats.size > 0 ? outputStats.size / stats.size : 0,
832
- has_checksums: false,
833
- created: outputStats.mtime.toISOString()
834
- };
835
- }
836
- async function addDirectoryToZipArchive(archive, dirPath, archivePrefix, options) {
837
- const entries = readdirSync(dirPath);
838
- for (const entry of entries) {
839
- const fullPath = join(dirPath, entry);
840
- const archivePath = join(archivePrefix, entry);
841
- const stats = lstatSync(fullPath);
842
- if (stats.isSymbolicLink()) {
843
- if (!options.follow_symlinks) {
844
- continue;
845
- }
846
- const targetStats = statSync(fullPath);
847
- if (targetStats.isDirectory()) {
848
- await addDirectoryToZipArchive(archive, fullPath, archivePath, options);
849
- } else {
850
- archive.file(fullPath, {
851
- name: archivePath,
852
- mode: options.preserve_permissions ? targetStats.mode : 420
853
- });
854
- }
855
- } else if (stats.isDirectory()) {
856
- await addDirectoryToZipArchive(archive, fullPath, archivePath, options);
857
- } else {
858
- archive.file(fullPath, {
859
- name: archivePath,
860
- mode: options.preserve_permissions ? stats.mode : 420
861
- });
862
- }
863
- }
864
- }
865
- async function extractTarGz(archive, destination, options) {
866
- const tarStream = await import('tar-stream');
867
- const extract2 = tarStream.extract();
868
- let extractedCount = 0;
869
- let skippedCount = 0;
870
- let errorCount = 0;
871
- const errors = [];
872
- const warnings = [];
873
- let totalExtractedSize = 0;
874
- let entryCount = 0;
875
- extract2.on("entry", async (header, stream, next) => {
876
- entryCount++;
877
- try {
878
- if (options.max_entries && entryCount > options.max_entries) {
879
- errors.push(
880
- createFulpackError(
881
- ERROR_CODES.DECOMPRESSION_BOMB,
882
- `Entry count exceeds maximum (${options.max_entries})`,
883
- "extract" /* EXTRACT */,
884
- {
885
- archive,
886
- details: { entry_count: entryCount, max_entries: options.max_entries }
887
- }
888
- )
889
- );
890
- stream.resume();
891
- next();
892
- return;
893
- }
894
- const pathError = validatePath(header.name, "extract" /* EXTRACT */, false);
895
- if (pathError) {
896
- errors.push({ ...pathError, archive });
897
- errorCount++;
898
- stream.resume();
899
- next();
900
- return;
901
- }
902
- const outputPath = join(destination, header.name);
903
- if (header.type === "directory") {
904
- if (!existsSync(outputPath)) {
905
- mkdirSync(outputPath, { recursive: true });
906
- }
907
- extractedCount++;
908
- stream.resume();
909
- next();
910
- } else if (header.type === "file") {
911
- if (existsSync(outputPath)) {
912
- if (options.overwrite === "error") {
913
- errors.push(
914
- createFulpackError(
915
- ERROR_CODES.EXTRACTION_FAILED,
916
- `Output file already exists: ${outputPath}`,
917
- "extract" /* EXTRACT */,
918
- { path: header.name, archive }
919
- )
920
- );
921
- errorCount++;
922
- stream.resume();
923
- next();
924
- return;
925
- } else if (options.overwrite === "skip") {
926
- warnings.push(`Skipped existing file: ${outputPath}`);
927
- skippedCount++;
928
- stream.resume();
929
- next();
930
- return;
931
- }
932
- }
933
- const parentDir = dirname(outputPath);
934
- if (!existsSync(parentDir)) {
935
- mkdirSync(parentDir, { recursive: true });
936
- }
937
- const writeStream = createWriteStream(outputPath);
938
- stream.on("data", (chunk) => {
939
- totalExtractedSize += chunk.length;
940
- if (options.max_size && totalExtractedSize > options.max_size) {
941
- stream.destroy();
942
- writeStream.destroy();
943
- errors.push(
944
- createFulpackError(
945
- ERROR_CODES.DECOMPRESSION_BOMB,
946
- `Total extracted size exceeds maximum (${options.max_size} bytes)`,
947
- "extract" /* EXTRACT */,
948
- {
949
- archive,
950
- path: header.name,
951
- details: { actual_size: totalExtractedSize, max_size: options.max_size }
952
- }
953
- )
954
- );
955
- errorCount++;
956
- next();
957
- return;
958
- }
959
- });
960
- stream.pipe(writeStream);
961
- writeStream.on("finish", () => {
962
- if (options.preserve_permissions && header.mode) {
963
- try {
964
- const { chmodSync } = __require("fs");
965
- chmodSync(outputPath, header.mode);
966
- } catch {
967
- }
968
- }
969
- extractedCount++;
970
- next();
971
- });
972
- writeStream.on("error", (error) => {
973
- errors.push(
974
- createFulpackError(
975
- ERROR_CODES.EXTRACTION_FAILED,
976
- `Failed to write file: ${error.message}`,
977
- "extract" /* EXTRACT */,
978
- { path: header.name, archive, details: { original_error: error } }
979
- )
980
- );
981
- errorCount++;
982
- next();
983
- });
984
- } else {
985
- warnings.push(`Skipped entry type ${header.type}: ${header.name}`);
986
- stream.resume();
987
- next();
988
- }
989
- } catch (error) {
990
- errors.push(
991
- createFulpackError(
992
- ERROR_CODES.EXTRACTION_FAILED,
993
- `Error processing entry: ${error instanceof Error ? error.message : "Unknown error"}`,
994
- "extract" /* EXTRACT */,
995
- { path: header.name, archive, details: { original_error: error } }
996
- )
997
- );
998
- errorCount++;
999
- stream.resume();
1000
- next();
1001
- }
1002
- });
1003
- const readStream = createReadStream(archive);
1004
- const gunzip = createGunzip();
1005
- return new Promise((resolve, reject) => {
1006
- extract2.on("finish", () => {
1007
- resolve({
1008
- extracted_count: extractedCount,
1009
- skipped_count: skippedCount,
1010
- error_count: errorCount,
1011
- errors: errors.length > 0 ? errors : void 0,
1012
- warnings: warnings.length > 0 ? warnings : void 0,
1013
- total_bytes: totalExtractedSize
1014
- });
1015
- });
1016
- extract2.on("error", (error) => {
1017
- reject(
1018
- new FulpackOperationError(
1019
- createFulpackError(
1020
- ERROR_CODES.EXTRACTION_FAILED,
1021
- `TAR.GZ extraction failed: ${error.message}`,
1022
- "extract" /* EXTRACT */,
1023
- { archive, details: { original_error: error } }
1024
- )
1025
- )
1026
- );
1027
- });
1028
- readStream.pipe(gunzip).pipe(extract2);
1029
- });
1030
- }
1031
- async function extractTar(archive, destination, options) {
1032
- const tarStream = await import('tar-stream');
1033
- const extract2 = tarStream.extract();
1034
- let extractedCount = 0;
1035
- let skippedCount = 0;
1036
- let errorCount = 0;
1037
- const errors = [];
1038
- const warnings = [];
1039
- let totalExtractedSize = 0;
1040
- let entryCount = 0;
1041
- extract2.on("entry", async (header, stream, next) => {
1042
- entryCount++;
1043
- try {
1044
- if (options.max_entries && entryCount > options.max_entries) {
1045
- errors.push(
1046
- createFulpackError(
1047
- ERROR_CODES.DECOMPRESSION_BOMB,
1048
- `Entry count exceeds maximum (${options.max_entries})`,
1049
- "extract" /* EXTRACT */,
1050
- {
1051
- archive,
1052
- details: { entry_count: entryCount, max_entries: options.max_entries }
1053
- }
1054
- )
1055
- );
1056
- stream.resume();
1057
- next();
1058
- return;
1059
- }
1060
- const pathError = validatePath(header.name, "extract" /* EXTRACT */, false);
1061
- if (pathError) {
1062
- errors.push({ ...pathError, archive });
1063
- errorCount++;
1064
- stream.resume();
1065
- next();
1066
- return;
1067
- }
1068
- const outputPath = join(destination, header.name);
1069
- if (header.type === "directory") {
1070
- if (!existsSync(outputPath)) {
1071
- mkdirSync(outputPath, { recursive: true });
1072
- }
1073
- extractedCount++;
1074
- stream.resume();
1075
- next();
1076
- } else if (header.type === "file") {
1077
- if (existsSync(outputPath)) {
1078
- if (options.overwrite === "error") {
1079
- errors.push(
1080
- createFulpackError(
1081
- ERROR_CODES.EXTRACTION_FAILED,
1082
- `Output file already exists: ${outputPath}`,
1083
- "extract" /* EXTRACT */,
1084
- { path: header.name, archive }
1085
- )
1086
- );
1087
- errorCount++;
1088
- stream.resume();
1089
- next();
1090
- return;
1091
- } else if (options.overwrite === "skip") {
1092
- warnings.push(`Skipped existing file: ${outputPath}`);
1093
- skippedCount++;
1094
- stream.resume();
1095
- next();
1096
- return;
1097
- }
1098
- }
1099
- const parentDir = dirname(outputPath);
1100
- if (!existsSync(parentDir)) {
1101
- mkdirSync(parentDir, { recursive: true });
1102
- }
1103
- const writeStream = createWriteStream(outputPath);
1104
- stream.on("data", (chunk) => {
1105
- totalExtractedSize += chunk.length;
1106
- if (options.max_size && totalExtractedSize > options.max_size) {
1107
- stream.destroy();
1108
- writeStream.destroy();
1109
- errors.push(
1110
- createFulpackError(
1111
- ERROR_CODES.DECOMPRESSION_BOMB,
1112
- `Total extracted size exceeds maximum (${options.max_size} bytes)`,
1113
- "extract" /* EXTRACT */,
1114
- {
1115
- archive,
1116
- path: header.name,
1117
- details: { actual_size: totalExtractedSize, max_size: options.max_size }
1118
- }
1119
- )
1120
- );
1121
- errorCount++;
1122
- next();
1123
- return;
1124
- }
1125
- });
1126
- stream.pipe(writeStream);
1127
- writeStream.on("finish", () => {
1128
- if (options.preserve_permissions && header.mode) {
1129
- try {
1130
- const { chmodSync } = __require("fs");
1131
- chmodSync(outputPath, header.mode);
1132
- } catch {
1133
- }
1134
- }
1135
- extractedCount++;
1136
- next();
1137
- });
1138
- writeStream.on("error", (error) => {
1139
- errors.push(
1140
- createFulpackError(
1141
- ERROR_CODES.EXTRACTION_FAILED,
1142
- `Failed to write file: ${error.message}`,
1143
- "extract" /* EXTRACT */,
1144
- { path: header.name, archive, details: { original_error: error } }
1145
- )
1146
- );
1147
- errorCount++;
1148
- next();
1149
- });
1150
- } else {
1151
- warnings.push(`Skipped entry type ${header.type}: ${header.name}`);
1152
- stream.resume();
1153
- next();
1154
- }
1155
- } catch (error) {
1156
- errors.push(
1157
- createFulpackError(
1158
- ERROR_CODES.EXTRACTION_FAILED,
1159
- `Error processing entry: ${error instanceof Error ? error.message : "Unknown error"}`,
1160
- "extract" /* EXTRACT */,
1161
- { path: header.name, archive, details: { original_error: error } }
1162
- )
1163
- );
1164
- errorCount++;
1165
- stream.resume();
1166
- next();
1167
- }
1168
- });
1169
- const readStream = createReadStream(archive);
1170
- return new Promise((resolve, reject) => {
1171
- extract2.on("finish", () => {
1172
- resolve({
1173
- extracted_count: extractedCount,
1174
- skipped_count: skippedCount,
1175
- error_count: errorCount,
1176
- errors: errors.length > 0 ? errors : void 0,
1177
- warnings: warnings.length > 0 ? warnings : void 0,
1178
- total_bytes: totalExtractedSize
1179
- });
1180
- });
1181
- extract2.on("error", (error) => {
1182
- reject(
1183
- new FulpackOperationError(
1184
- createFulpackError(
1185
- ERROR_CODES.EXTRACTION_FAILED,
1186
- `TAR extraction failed: ${error.message}`,
1187
- "extract" /* EXTRACT */,
1188
- { archive, details: { original_error: error } }
1189
- )
1190
- )
1191
- );
1192
- });
1193
- readStream.pipe(extract2);
1194
- });
1195
- }
1196
- async function extractZip(archive, destination, options) {
1197
- const unzipper = await import('unzipper');
1198
- let extractedCount = 0;
1199
- let skippedCount = 0;
1200
- let errorCount = 0;
1201
- const errors = [];
1202
- const warnings = [];
1203
- let totalExtractedSize = 0;
1204
- let entryCount = 0;
1205
- return new Promise((resolve, reject) => {
1206
- const readStream = createReadStream(archive);
1207
- readStream.pipe(unzipper.Parse()).on("entry", async (entry) => {
1208
- entryCount++;
1209
- try {
1210
- if (options.max_entries && entryCount > options.max_entries) {
1211
- errors.push(
1212
- createFulpackError(
1213
- ERROR_CODES.DECOMPRESSION_BOMB,
1214
- `Entry count exceeds maximum (${options.max_entries})`,
1215
- "extract" /* EXTRACT */,
1216
- {
1217
- archive,
1218
- details: { entry_count: entryCount, max_entries: options.max_entries }
1219
- }
1220
- )
1221
- );
1222
- entry.autodrain();
1223
- return;
1224
- }
1225
- const pathError = validatePath(entry.path, "extract" /* EXTRACT */, false);
1226
- if (pathError) {
1227
- errors.push({ ...pathError, archive });
1228
- errorCount++;
1229
- entry.autodrain();
1230
- return;
1231
- }
1232
- const outputPath = join(destination, entry.path);
1233
- if (entry.type === "Directory") {
1234
- if (!existsSync(outputPath)) {
1235
- mkdirSync(outputPath, { recursive: true });
1236
- }
1237
- extractedCount++;
1238
- entry.autodrain();
1239
- } else if (entry.type === "File") {
1240
- if (existsSync(outputPath)) {
1241
- if (options.overwrite === "error") {
1242
- errors.push(
1243
- createFulpackError(
1244
- ERROR_CODES.EXTRACTION_FAILED,
1245
- `Output file already exists: ${outputPath}`,
1246
- "extract" /* EXTRACT */,
1247
- { path: entry.path, archive }
1248
- )
1249
- );
1250
- errorCount++;
1251
- entry.autodrain();
1252
- return;
1253
- } else if (options.overwrite === "skip") {
1254
- warnings.push(`Skipped existing file: ${outputPath}`);
1255
- skippedCount++;
1256
- entry.autodrain();
1257
- return;
1258
- }
1259
- }
1260
- const parentDir = dirname(outputPath);
1261
- if (!existsSync(parentDir)) {
1262
- mkdirSync(parentDir, { recursive: true });
1263
- }
1264
- const writeStream = createWriteStream(outputPath);
1265
- entry.on("data", (chunk) => {
1266
- totalExtractedSize += chunk.length;
1267
- if (options.max_size && totalExtractedSize > options.max_size) {
1268
- entry.destroy();
1269
- writeStream.destroy();
1270
- errors.push(
1271
- createFulpackError(
1272
- ERROR_CODES.DECOMPRESSION_BOMB,
1273
- `Total extracted size exceeds maximum (${options.max_size} bytes)`,
1274
- "extract" /* EXTRACT */,
1275
- {
1276
- archive,
1277
- path: entry.path,
1278
- details: { actual_size: totalExtractedSize, max_size: options.max_size }
1279
- }
1280
- )
1281
- );
1282
- errorCount++;
1283
- return;
1284
- }
1285
- });
1286
- entry.pipe(writeStream);
1287
- writeStream.on("finish", () => {
1288
- extractedCount++;
1289
- });
1290
- writeStream.on("error", (error) => {
1291
- errors.push(
1292
- createFulpackError(
1293
- ERROR_CODES.EXTRACTION_FAILED,
1294
- `Failed to write file: ${error.message}`,
1295
- "extract" /* EXTRACT */,
1296
- { path: entry.path, archive, details: { original_error: error } }
1297
- )
1298
- );
1299
- errorCount++;
1300
- });
1301
- } else {
1302
- warnings.push(`Skipped entry type ${entry.type}: ${entry.path}`);
1303
- entry.autodrain();
1304
- }
1305
- } catch (error) {
1306
- errors.push(
1307
- createFulpackError(
1308
- ERROR_CODES.EXTRACTION_FAILED,
1309
- `Error processing entry: ${error instanceof Error ? error.message : "Unknown error"}`,
1310
- "extract" /* EXTRACT */,
1311
- { path: entry.path, archive, details: { original_error: error } }
1312
- )
1313
- );
1314
- errorCount++;
1315
- entry.autodrain();
1316
- }
1317
- }).on("close", () => {
1318
- resolve({
1319
- extracted_count: extractedCount,
1320
- skipped_count: skippedCount,
1321
- error_count: errorCount,
1322
- errors: errors.length > 0 ? errors : void 0,
1323
- warnings: warnings.length > 0 ? warnings : void 0,
1324
- total_bytes: totalExtractedSize
1325
- });
1326
- }).on("error", (error) => {
1327
- reject(
1328
- new FulpackOperationError(
1329
- createFulpackError(
1330
- ERROR_CODES.EXTRACTION_FAILED,
1331
- `ZIP extraction failed: ${error.message}`,
1332
- "extract" /* EXTRACT */,
1333
- { archive, details: { original_error: error } }
1334
- )
1335
- )
1336
- );
1337
- });
1338
- });
1339
- }
1340
- async function scanTar(archive, options) {
1341
- const tarStream = await import('tar-stream');
1342
- const extract2 = tarStream.extract();
1343
- const entries = [];
1344
- let entryCount = 0;
1345
- return new Promise((resolve, reject) => {
1346
- extract2.on("entry", async (header, stream, next) => {
1347
- entryCount++;
1348
- if (options.max_entries && entryCount > options.max_entries) {
1349
- stream.resume();
1350
- next();
1351
- reject(
1352
- new FulpackOperationError(
1353
- createFulpackError(
1354
- ERROR_CODES.DECOMPRESSION_BOMB,
1355
- `Entry count exceeds maximum (${options.max_entries})`,
1356
- "scan" /* SCAN */,
1357
- {
1358
- archive,
1359
- details: { entry_count: entryCount, max_entries: options.max_entries }
1360
- }
1361
- )
1362
- )
1363
- );
1364
- return;
1365
- }
1366
- const entry = {
1367
- path: header.name,
1368
- type: header.type === "directory" ? "directory" /* DIRECTORY */ : header.type === "symlink" ? "symlink" /* SYMLINK */ : "file" /* FILE */,
1369
- size: header.size || 0,
1370
- modified: header.mtime ? header.mtime.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
1371
- ...options.include_metadata && header.mode !== void 0 && header.mode !== null ? { mode: `0${(header.mode & 511).toString(8)}` } : {},
1372
- ...options.include_metadata && header.type === "symlink" && header.linkname ? { symlink_target: header.linkname } : {}
1373
- };
1374
- entries.push(entry);
1375
- stream.resume();
1376
- next();
1377
- });
1378
- extract2.on("finish", () => {
1379
- resolve(entries);
1380
- });
1381
- extract2.on("error", (error) => {
1382
- reject(
1383
- new FulpackOperationError(
1384
- createFulpackError(
1385
- ERROR_CODES.ARCHIVE_CORRUPT,
1386
- `TAR scanning failed: ${error.message}`,
1387
- "scan" /* SCAN */,
1388
- { archive, details: { original_error: error } }
1389
- )
1390
- )
1391
- );
1392
- });
1393
- const readStream = createReadStream(archive);
1394
- readStream.pipe(extract2);
1395
- });
1396
- }
1397
- async function scanTarGz(archive, options) {
1398
- const tarStream = await import('tar-stream');
1399
- const extract2 = tarStream.extract();
1400
- const entries = [];
1401
- let entryCount = 0;
1402
- return new Promise((resolve, reject) => {
1403
- extract2.on("entry", async (header, stream, next) => {
1404
- entryCount++;
1405
- if (options.max_entries && entryCount > options.max_entries) {
1406
- stream.resume();
1407
- next();
1408
- reject(
1409
- new FulpackOperationError(
1410
- createFulpackError(
1411
- ERROR_CODES.DECOMPRESSION_BOMB,
1412
- `Entry count exceeds maximum (${options.max_entries})`,
1413
- "scan" /* SCAN */,
1414
- {
1415
- archive,
1416
- details: { entry_count: entryCount, max_entries: options.max_entries }
1417
- }
1418
- )
1419
- )
1420
- );
1421
- return;
1422
- }
1423
- const entry = {
1424
- path: header.name,
1425
- type: header.type === "directory" ? "directory" /* DIRECTORY */ : header.type === "symlink" ? "symlink" /* SYMLINK */ : "file" /* FILE */,
1426
- size: header.size || 0,
1427
- modified: header.mtime ? header.mtime.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
1428
- ...options.include_metadata && header.mode !== void 0 && header.mode !== null ? { mode: `0${(header.mode & 511).toString(8)}` } : {},
1429
- ...options.include_metadata && header.type === "symlink" && header.linkname ? { symlink_target: header.linkname } : {}
1430
- };
1431
- entries.push(entry);
1432
- stream.resume();
1433
- next();
1434
- });
1435
- extract2.on("finish", () => {
1436
- resolve(entries);
1437
- });
1438
- extract2.on("error", (error) => {
1439
- reject(
1440
- new FulpackOperationError(
1441
- createFulpackError(
1442
- ERROR_CODES.ARCHIVE_CORRUPT,
1443
- `TAR.GZ scanning failed: ${error.message}`,
1444
- "scan" /* SCAN */,
1445
- { archive, details: { original_error: error } }
1446
- )
1447
- )
1448
- );
1449
- });
1450
- const readStream = createReadStream(archive);
1451
- const gunzip = createGunzip();
1452
- readStream.pipe(gunzip).pipe(extract2);
1453
- });
1454
- }
1455
- async function scanZip(archive, options) {
1456
- const unzipper = await import('unzipper');
1457
- const entries = [];
1458
- let entryCount = 0;
1459
- return new Promise((resolve, reject) => {
1460
- createReadStream(archive).pipe(unzipper.Parse()).on("entry", (entry) => {
1461
- const typedEntry = entry;
1462
- entryCount++;
1463
- if (options.max_entries && entryCount > options.max_entries) {
1464
- typedEntry.autodrain();
1465
- reject(
1466
- new FulpackOperationError(
1467
- createFulpackError(
1468
- ERROR_CODES.DECOMPRESSION_BOMB,
1469
- `Entry count exceeds maximum (${options.max_entries})`,
1470
- "scan" /* SCAN */,
1471
- {
1472
- archive,
1473
- details: { entry_count: entryCount, max_entries: options.max_entries }
1474
- }
1475
- )
1476
- )
1477
- );
1478
- return;
1479
- }
1480
- const uncompressedSize = typedEntry.vars?.uncompressedSize || typedEntry.props?.size || 0;
1481
- const compressedSize = typedEntry.vars?.compressedSize || typedEntry.props?.compressedSize || 0;
1482
- const archiveEntry = {
1483
- path: typedEntry.path,
1484
- type: typedEntry.type === "Directory" ? "directory" /* DIRECTORY */ : "file" /* FILE */,
1485
- size: uncompressedSize,
1486
- modified: typedEntry.vars?.lastModifiedTime ? new Date(typedEntry.vars.lastModifiedTime).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
1487
- ...options.include_metadata ? {
1488
- compressed_size: compressedSize,
1489
- mode: "0644"
1490
- // ZIP format doesn't preserve Unix permissions consistently
1491
- } : {}
1492
- };
1493
- entries.push(archiveEntry);
1494
- typedEntry.autodrain();
1495
- }).on("error", (error) => {
1496
- reject(
1497
- new FulpackOperationError(
1498
- createFulpackError(
1499
- ERROR_CODES.ARCHIVE_CORRUPT,
1500
- `ZIP scanning failed: ${error.message}`,
1501
- "scan" /* SCAN */,
1502
- { archive, details: { original_error: error } }
1503
- )
1504
- )
1505
- );
1506
- }).on("finish", () => {
1507
- resolve(entries);
1508
- });
1509
- });
1510
- }
1511
-
1512
- // src/fulpack/index.ts
1513
- var FULPACK_VERSION = "1.0.0";
1514
-
1515
- export { ArchiveFormat, ERROR_CODES, EntryType, FULPACK_VERSION, FulpackOperationError, Operation, checkDecompressionBomb, create, createFulpackError, extract, hasPathTraversal, info, isAbsolutePath, scan, validatePath, verify };
1
+ export { ERROR_CODES, FULPACK_VERSION, FulpackOperationError, checkDecompressionBomb, create, createFulpackError, extract, hasPathTraversal, info, isAbsolutePath, scan, validatePath, verify } from '../chunk-P3DNOZOG.js';
2
+ export { ArchiveFormat, EntryType, Operation } from '../chunk-PQ6TKPUN.js';
3
+ import '../chunk-UP2VWCW5.js';
1516
4
  //# sourceMappingURL=index.js.map
1517
5
  //# sourceMappingURL=index.js.map