@blamejs/core 0.12.7 → 0.12.9

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.
@@ -65,6 +65,8 @@ var compliance = lazyRequire(function () { return require("../compliance"); });
65
65
  // module graph (CLI tools, stand-alone backup runners). The db()
66
66
  // callable resolves on first access.
67
67
  var dbModuleLazy = lazyRequire(function () { return require("../db"); });
68
+ var archiveLazy = lazyRequire(function () { return require("../archive"); });
69
+ var archiveAdaptersLazy = lazyRequire(function () { return require("../archive-adapters"); });
68
70
  var { defineClass } = require("../framework-error");
69
71
 
70
72
  var BackupError = defineClass("BackupError");
@@ -993,6 +995,7 @@ module.exports = {
993
995
  create: create,
994
996
  diskStorage: diskStorage,
995
997
  bundleAdapterStorage: bundleAdapterStorage,
998
+ migrate: migrate,
996
999
  recommendedFiles: recommendedFiles,
997
1000
  runInWorker: runInWorker,
998
1001
  verifyManifestSignature: verifyManifestSignature,
@@ -1055,6 +1058,35 @@ function bundleAdapterStorage(opts) {
1055
1058
  "bundleAdapterStorage: adapter missing method '" + required[i] + "'");
1056
1059
  }
1057
1060
  }
1061
+ // v0.12.8 — `format: "tar"` becomes the default for new bundles.
1062
+ // `format: "directory"` opts back into the v0.12.7 file-by-file
1063
+ // layout for operators with existing bundles. The format is
1064
+ // operator-supplied so a single backup engine can transition over
1065
+ // time + b.backup.migrate() handles the directory → tar conversion.
1066
+ var format = opts.format || "tar";
1067
+ if (format !== "tar" && format !== "tar.gz" && format !== "directory") {
1068
+ throw new BackupError("backup/bad-format",
1069
+ "bundleAdapterStorage: format must be \"tar\" (default) | \"tar.gz\" (v0.12.9 compressed) | \"directory\" (legacy v0.12.7)");
1070
+ }
1071
+ // Codex P2 on v0.12.8 PR #159 — tar mode builds the whole archive
1072
+ // in memory before adapter.writeFile because the v0.12.8 adapter
1073
+ // contract is bytes-in (no writeStream method). The OOM-prevention
1074
+ // gate is maxBundleBytes: writeBundle pre-walks the source tree,
1075
+ // sums file sizes, and refuses upfront if the projected uncompressed
1076
+ // tar would exceed the cap. Default 8 GiB — accommodates typical
1077
+ // db + mail-spool + log bundles while refusing pathological inputs.
1078
+ // Defer-with-condition for true streaming: when the adapter
1079
+ // contract gains writeStream(key) (slated for v0.13+ alongside
1080
+ // multipart S3 upload primitives), this path switches to
1081
+ // tarBuilder.toStream() and writes chunks as they're produced.
1082
+ var maxBundleBytes = opts.maxBundleBytes !== undefined
1083
+ ? opts.maxBundleBytes
1084
+ : 8 * 1024 * 1024 * 1024; // allow:raw-byte-literal — 8 GiB default cap; uses C.BYTES.bytes covers numeric-literal rule
1085
+ if (!numericBounds.isPositiveFiniteInt(maxBundleBytes)) { // allow:inline-numeric-bounds-cascade — required-with-default opt
1086
+ throw new BackupError("backup/bad-arg",
1087
+ "bundleAdapterStorage: maxBundleBytes must be a positive finite integer; got " +
1088
+ numericBounds.shape(opts.maxBundleBytes));
1089
+ }
1058
1090
 
1059
1091
  function _ensureBundleId(bundleId) {
1060
1092
  if (!_isValidBundleId(bundleId)) {
@@ -1078,6 +1110,20 @@ function bundleAdapterStorage(opts) {
1078
1110
  return out;
1079
1111
  }
1080
1112
 
1113
+ // Tar-format bundle storage stores the whole bundle as a single
1114
+ // key under `<bundleId>/bundle.tar` (or `<bundleId>/bundle.tar.gz`
1115
+ // for the v0.12.9 compressed variant). The marker is named that
1116
+ // way so listBundles + hasBundle can locate either format by key
1117
+ // prefix walk.
1118
+ var TAR_KEY_SUFFIX = "/bundle.tar";
1119
+ var TAR_GZ_KEY_SUFFIX = "/bundle.tar.gz";
1120
+
1121
+ function _hasBundleKey(bundleId, format) {
1122
+ if (format === "tar") return adapter.hasKey(bundleId + TAR_KEY_SUFFIX);
1123
+ if (format === "tar.gz") return adapter.hasKey(bundleId + TAR_GZ_KEY_SUFFIX);
1124
+ return adapter.hasKey(bundleId + "/manifest.json");
1125
+ }
1126
+
1081
1127
  return {
1082
1128
  name: "adapter",
1083
1129
  async writeBundle(bundleId, sourceDir) {
@@ -1086,16 +1132,58 @@ function bundleAdapterStorage(opts) {
1086
1132
  throw new BackupError("backup/no-source",
1087
1133
  "writeBundle: sourceDir does not exist: " + sourceDir);
1088
1134
  }
1089
- var alreadyHas = await adapter.hasKey(bundleId + "/manifest.json");
1135
+ var alreadyHas = await _hasBundleKey(bundleId, format);
1090
1136
  if (alreadyHas) {
1091
1137
  throw new BackupError("backup/bundle-exists",
1092
1138
  "writeBundle: bundle '" + bundleId + "' already exists in storage");
1093
1139
  }
1094
- var relPaths = _walkDirSync(sourceDir, []);
1095
- for (var i = 0; i < relPaths.length; i += 1) {
1096
- var rel = relPaths[i];
1097
- var bytes = nodeFs.readFileSync(nodePath.join(sourceDir, rel));
1098
- await adapter.writeFile(bundleId + "/" + rel, bytes);
1140
+ if (format === "tar" || format === "tar.gz") {
1141
+ // Pack the source-directory tree into a single tar archive +
1142
+ // store under one key. Composes b.archive.tar (+ b.archive.gz
1143
+ // for the tar.gz variant which adds gzip compression on the
1144
+ // wire). Bundle sizes drop ~3-5× on text-heavy backups
1145
+ // (databases, JSON exports, mail spools) under tar.gz.
1146
+ //
1147
+ // Codex P2 on v0.12.8 PR #159 — tar bytes are materialized in
1148
+ // memory because the v0.12.8 adapter contract is bytes-in
1149
+ // (writeFile takes a Buffer, no writeStream method). The
1150
+ // maxBundleBytes pre-walk computes the uncompressed payload
1151
+ // size (file bytes only — tar header overhead is bounded at
1152
+ // ~512 B per entry + 1024 B trailer) and refuses upfront so
1153
+ // pathological inputs throw `backup/bundle-too-large` instead
1154
+ // of OOM. The defer-with-condition for true streaming is
1155
+ // gated on the adapter contract gaining writeStream(key).
1156
+ var relPaths = _walkDirSync(sourceDir, []);
1157
+ var projectedBytes = 0;
1158
+ for (var pi = 0; pi < relPaths.length; pi += 1) {
1159
+ var stat = nodeFs.statSync(nodePath.join(sourceDir, relPaths[pi]));
1160
+ projectedBytes += stat.size;
1161
+ }
1162
+ if (projectedBytes > maxBundleBytes) {
1163
+ throw new BackupError("backup/bundle-too-large",
1164
+ "writeBundle: projected uncompressed bundle " + projectedBytes +
1165
+ " bytes exceeds maxBundleBytes=" + maxBundleBytes +
1166
+ " — split the source tree across multiple bundles or raise the cap");
1167
+ }
1168
+ var t = archiveLazy().tar();
1169
+ for (var i = 0; i < relPaths.length; i += 1) {
1170
+ var rel = relPaths[i];
1171
+ var bytes = nodeFs.readFileSync(nodePath.join(sourceDir, rel));
1172
+ t.addFile(rel, bytes);
1173
+ }
1174
+ var keySuffix = format === "tar.gz" ? TAR_GZ_KEY_SUFFIX : TAR_KEY_SUFFIX;
1175
+ var payloadBytes = format === "tar.gz"
1176
+ ? archiveLazy().gz(t.toBuffer()).toBuffer()
1177
+ : t.toBuffer();
1178
+ await adapter.writeFile(bundleId + keySuffix, payloadBytes);
1179
+ return;
1180
+ }
1181
+ // Directory format (v0.12.7 layout).
1182
+ var dirRelPaths = _walkDirSync(sourceDir, []);
1183
+ for (var j = 0; j < dirRelPaths.length; j += 1) {
1184
+ var dirRel = dirRelPaths[j];
1185
+ var dirBytes = nodeFs.readFileSync(nodePath.join(sourceDir, dirRel));
1186
+ await adapter.writeFile(bundleId + "/" + dirRel, dirBytes);
1099
1187
  }
1100
1188
  },
1101
1189
  async readBundle(bundleId, destDir) {
@@ -1104,20 +1192,51 @@ function bundleAdapterStorage(opts) {
1104
1192
  throw new BackupError("backup/dest-exists",
1105
1193
  "readBundle: destDir already exists: " + destDir);
1106
1194
  }
1107
- var keys = await adapter.listKeys(bundleId + "/");
1108
- if (keys.length === 0) {
1195
+ // Detect which format this bundle is in — operators with mixed
1196
+ // pre-v0.12.8 + post-v0.12.8 (+ v0.12.9 tar.gz) bundles can read
1197
+ // every flavor back.
1198
+ var hasTar = await adapter.hasKey(bundleId + TAR_KEY_SUFFIX);
1199
+ var hasTarGz = await adapter.hasKey(bundleId + TAR_GZ_KEY_SUFFIX);
1200
+ var hasManifest = await adapter.hasKey(bundleId + "/manifest.json");
1201
+ if (!hasTar && !hasTarGz && !hasManifest) {
1109
1202
  throw new BackupError("backup/bundle-not-found",
1110
1203
  "readBundle: '" + bundleId + "' not in storage");
1111
1204
  }
1112
1205
  atomicFile.ensureDir(destDir);
1206
+ if (hasTarGz) {
1207
+ // Codex P1/P2 on v0.12.9 PR #160 — propagate maxBundleBytes
1208
+ // to the gz restore path + disable the expansion-ratio cap.
1209
+ // archive.read.gz defaults (1 GiB output / 100× ratio) are
1210
+ // bomb-defense settings appropriate for adversarial input;
1211
+ // this is a SELF-AUTHORED bundle the writeBundle path
1212
+ // produced under maxBundleBytes — the restore contract is
1213
+ // "decompress to at most what was permitted on write." A
1214
+ // zero-filled DB file can easily hit >100× compression
1215
+ // ratio; without these opts the same primitive writes
1216
+ // bundles it can't read back.
1217
+ var gzBytes = await adapter.readFile(bundleId + TAR_GZ_KEY_SUFFIX);
1218
+ var gzReader = archiveLazy().read.gz(archiveAdaptersLazy().buffer(gzBytes), {
1219
+ maxDecompressedBytes: maxBundleBytes,
1220
+ maxExpansionRatio: 0,
1221
+ });
1222
+ var tarReader = gzReader.asTar();
1223
+ await tarReader.extract({ destination: destDir });
1224
+ return;
1225
+ }
1226
+ if (hasTar) {
1227
+ var tarBytes = await adapter.readFile(bundleId + TAR_KEY_SUFFIX);
1228
+ var reader = archiveLazy().read.tar(archiveAdaptersLazy().buffer(tarBytes));
1229
+ await reader.extract({ destination: destDir });
1230
+ return;
1231
+ }
1232
+ // Directory format readback (v0.12.7 layout).
1233
+ var keys = await adapter.listKeys(bundleId + "/");
1113
1234
  for (var i = 0; i < keys.length; i += 1) {
1114
1235
  var key = keys[i];
1115
1236
  var prefix = bundleId + "/";
1116
1237
  if (key.indexOf(prefix) !== 0) continue;
1117
1238
  var rel = key.slice(prefix.length);
1118
- // Path-safety: rel must not escape destDir. Reuse the
1119
- // verifyExtractionPath dual-check primitive when the dest
1120
- // already exists (writeable directory just created).
1239
+ // Path-safety: rel must not escape destDir.
1121
1240
  var destPath = nodePath.join(destDir, rel);
1122
1241
  var resolvedDest = nodePath.resolve(destPath);
1123
1242
  var resolvedRoot = nodePath.resolve(destDir);
@@ -1129,7 +1248,13 @@ function bundleAdapterStorage(opts) {
1129
1248
  }
1130
1249
  atomicFile.ensureDir(nodePath.dirname(destPath));
1131
1250
  var bytes = await adapter.readFile(key);
1132
- nodeFs.writeFileSync(destPath, bytes);
1251
+ // Exclusive-create (wx) carries the v0.12.7 atomic-rollback
1252
+ // contract: readBundle refuses to overwrite pre-existing
1253
+ // files at destPath. Combined with the upfront destDir check
1254
+ // above (refuses if destDir already exists), the only way
1255
+ // wx fires here is a symlink swap between ensureDir and write
1256
+ // — which the framework refuses rather than following.
1257
+ nodeFs.writeFileSync(destPath, bytes, { flag: "wx", mode: 0o600 });
1133
1258
  }
1134
1259
  },
1135
1260
  async listBundles() {
@@ -1175,7 +1300,18 @@ function bundleAdapterStorage(opts) {
1175
1300
  },
1176
1301
  async hasBundle(bundleId) {
1177
1302
  _ensureBundleId(bundleId);
1178
- return adapter.hasKey(bundleId + "/manifest.json");
1303
+ // Format-aware: check the storage layout's marker key. Tar
1304
+ // bundles store under <bid>/bundle.tar; tar.gz bundles store
1305
+ // under <bid>/bundle.tar.gz; directory bundles store under
1306
+ // <bid>/manifest.json. Operators with a mixed bundle set
1307
+ // (some tar, some tar.gz, some directory) get true for any.
1308
+ var tarKey = bundleId + TAR_KEY_SUFFIX;
1309
+ var tarGzKey = bundleId + TAR_GZ_KEY_SUFFIX;
1310
+ var dirKey = bundleId + "/manifest.json";
1311
+ if (await adapter.hasKey(tarKey)) return true;
1312
+ if (await adapter.hasKey(tarGzKey)) return true;
1313
+ if (await adapter.hasKey(dirKey)) return true;
1314
+ return false;
1179
1315
  },
1180
1316
  };
1181
1317
  }
@@ -1244,3 +1380,91 @@ bundleAdapterStorage.fsAdapter = function (fsOpts) {
1244
1380
  },
1245
1381
  };
1246
1382
  };
1383
+
1384
+ // ---- v0.12.8: migrate ----------------------------------------------------
1385
+
1386
+ /**
1387
+ * @primitive b.backup.migrate
1388
+ * @signature b.backup.migrate(opts)
1389
+ * @since 0.12.8
1390
+ * @status stable
1391
+ * @related b.backup.bundleAdapterStorage
1392
+ *
1393
+ * One-shot helper that walks an operator's directory-tree-format
1394
+ * bundle (v0.12.7 layout) and writes the same content as a tar-format
1395
+ * bundle via the v0.12.8 `bundleAdapterStorage`. Idempotent: re-
1396
+ * running on an already-migrated bundle is a no-op. Source stays in
1397
+ * place by default; operators with explicit transition windows opt
1398
+ * into the inline replace via `deleteSourceOnSuccess: true`.
1399
+ *
1400
+ * @opts
1401
+ * from: bundleAdapterStorage with format: "directory",
1402
+ * to: bundleAdapterStorage with format: "tar",
1403
+ * bundleId: string (single-bundle migrate; omit to migrate all),
1404
+ * deleteSourceOnSuccess: boolean (default false; explicit opt-in),
1405
+ *
1406
+ * @example
1407
+ * var from = b.backup.bundleAdapterStorage({
1408
+ * adapter: b.backup.bundleAdapterStorage.fsAdapter({ root: "/var/backups-v7" }),
1409
+ * format: "directory",
1410
+ * });
1411
+ * var to = b.backup.bundleAdapterStorage({
1412
+ * adapter: b.backup.bundleAdapterStorage.fsAdapter({ root: "/var/backups-v8" }),
1413
+ * format: "tar",
1414
+ * });
1415
+ * await b.backup.migrate({ from: from, to: to });
1416
+ */
1417
+ async function migrate(opts) {
1418
+ opts = opts || {};
1419
+ if (!opts.from || typeof opts.from.readBundle !== "function" ||
1420
+ typeof opts.from.listBundles !== "function") {
1421
+ throw new BackupError("backup/bad-from",
1422
+ "migrate: opts.from must be a storage backend (got " + typeof opts.from + ")");
1423
+ }
1424
+ if (!opts.to || typeof opts.to.writeBundle !== "function" ||
1425
+ typeof opts.to.hasBundle !== "function") {
1426
+ throw new BackupError("backup/bad-to",
1427
+ "migrate: opts.to must be a storage backend (got " + typeof opts.to + ")");
1428
+ }
1429
+ var ids;
1430
+ if (opts.bundleId) {
1431
+ if (!_isValidBundleId(opts.bundleId)) {
1432
+ throw new BackupError("backup/bad-bundle-id",
1433
+ "migrate: bundleId must match the framework's timestamp+suffix format");
1434
+ }
1435
+ ids = [opts.bundleId];
1436
+ } else {
1437
+ var list = await opts.from.listBundles();
1438
+ ids = list.map(function (b) { return b.bundleId; });
1439
+ }
1440
+ var migrated = 0;
1441
+ var skipped = 0;
1442
+ for (var i = 0; i < ids.length; i += 1) {
1443
+ var bid = ids[i];
1444
+ // Idempotency: skip if destination already has the bundle.
1445
+ if (await opts.to.hasBundle(bid)) {
1446
+ skipped += 1;
1447
+ continue;
1448
+ }
1449
+ // Stage source-bundle into a tmp dir, then write via destination.
1450
+ var tmpDir = nodeFs.mkdtempSync(nodePath.join(os.tmpdir(),
1451
+ "blamejs-backup-migrate-" + bid + "-"));
1452
+ var stageDir = nodePath.join(tmpDir, "bundle");
1453
+ try {
1454
+ await opts.from.readBundle(bid, stageDir);
1455
+ await opts.to.writeBundle(bid, stageDir);
1456
+ migrated += 1;
1457
+ if (opts.deleteSourceOnSuccess === true) {
1458
+ if (typeof opts.from.deleteBundle === "function") {
1459
+ await opts.from.deleteBundle(bid);
1460
+ }
1461
+ }
1462
+ } finally {
1463
+ try { nodeFs.rmSync(tmpDir, { recursive: true, force: true }); }
1464
+ catch (_e) { /* drop-silent */ }
1465
+ }
1466
+ }
1467
+ return { migrated: migrated, skipped: skipped, total: ids.length };
1468
+ }
1469
+
1470
+
@@ -902,6 +902,7 @@ module.exports = {
902
902
  inspect: inspect,
903
903
  zipBombPolicy: zipBombPolicy,
904
904
  entryTypePolicy: entryTypePolicy,
905
+ tarEntryPolicy: tarEntryPolicy,
905
906
  };
906
907
 
907
908
  // ---- v0.12.7 extensions ---------------------------------------------------
@@ -1039,3 +1040,42 @@ function entryTypePolicy(opts) {
1039
1040
  sockets: opts.sockets === true,
1040
1041
  });
1041
1042
  }
1043
+
1044
+ /**
1045
+ * @primitive b.guardArchive.tarEntryPolicy
1046
+ * @signature b.guardArchive.tarEntryPolicy(opts)
1047
+ * @since 0.12.8
1048
+ * @status stable
1049
+ * @related b.guardArchive.entryTypePolicy, b.archive.read.tar
1050
+ *
1051
+ * Tar-specific entry-type policy. Same shape as `entryTypePolicy`
1052
+ * but explicitly named for tar's typeflag vocabulary (1=hardlink,
1053
+ * 2=symlink, 3=char-device, 4=block-device, 6=FIFO, 7=contiguous-
1054
+ * file) so call sites read clearly when the operator's intent is
1055
+ * tar-specific. Defaults refuse every dangerous typeflag. Operators
1056
+ * opting symlinks / hardlinks in get the link target routed through
1057
+ * `b.guardFilename.verifyExtractionPath`'s realpath-on-target check
1058
+ * (defends CVE-2026-23745 / 24842 node-tar path-resolution divergence
1059
+ * class).
1060
+ *
1061
+ * @opts
1062
+ * symlinks: false,
1063
+ * hardlinks: false,
1064
+ * devices: false,
1065
+ * fifos: false,
1066
+ * sockets: false,
1067
+ *
1068
+ * @example
1069
+ * var policy = b.guardArchive.tarEntryPolicy({ symlinks: true });
1070
+ * await b.safeArchive.extract({
1071
+ * source, destination, entryTypePolicy: policy,
1072
+ * allowDangerous: { symlinks: true },
1073
+ * });
1074
+ */
1075
+ function tarEntryPolicy(opts) {
1076
+ // Same shape as entryTypePolicy; aliased for tar-specific call-site
1077
+ // readability. The implementation is intentionally identical — the
1078
+ // policy-object shape is format-neutral, only the typeflag mapping
1079
+ // in the reader differs.
1080
+ return entryTypePolicy(opts);
1081
+ }
@@ -45,6 +45,8 @@ var SafeArchiveError = defineClass("SafeArchiveError", { alwaysPermanent: true }
45
45
 
46
46
  var archiveRead = lazyRequire(function () { return require("./archive-read"); });
47
47
  var archiveAdapters = lazyRequire(function () { return require("./archive-adapters"); });
48
+ var archiveTarRead = lazyRequire(function () { return require("./archive-tar-read"); });
49
+ var archiveGz = lazyRequire(function () { return require("./archive-gz"); });
48
50
 
49
51
  // ---- Format sniffing ----------------------------------------------------
50
52
 
@@ -178,18 +180,43 @@ async function extract(opts) {
178
180
  var sniff = await _sniffMagic(source);
179
181
  format = sniff.format;
180
182
  }
181
- if (format !== "zip") {
183
+ var reader;
184
+ if (format === "zip") {
185
+ reader = archiveRead().zip(source, {
186
+ bombPolicy: opts.bombPolicy,
187
+ entryTypePolicy: opts.entryTypePolicy,
188
+ guardProfile: opts.guardProfile,
189
+ audit: opts.audit,
190
+ });
191
+ } else if (format === "tar") {
192
+ reader = archiveTarRead().tar(source, {
193
+ bombPolicy: opts.bombPolicy,
194
+ entryTypePolicy: opts.entryTypePolicy,
195
+ guardProfile: opts.guardProfile,
196
+ audit: opts.audit,
197
+ });
198
+ } else if (format === "tar.gz") {
199
+ // gzip envelope around tar — safeDecompress caps run on the gz
200
+ // layer before the tar walker ever sees a decompressed byte.
201
+ reader = archiveGz().read.gz(source, {
202
+ maxDecompressedBytes: opts.maxDecompressedBytes,
203
+ maxExpansionRatio: opts.maxExpansionRatio,
204
+ audit: opts.audit,
205
+ }).asTar({
206
+ bombPolicy: opts.bombPolicy,
207
+ entryTypePolicy: opts.entryTypePolicy,
208
+ guardProfile: opts.guardProfile,
209
+ audit: opts.audit,
210
+ });
211
+ } else {
182
212
  throw new SafeArchiveError("safe-archive/format-unsupported",
183
- "extract: format=" + JSON.stringify(format) + " — v0.12.7 ships ZIP only " +
184
- "(tar lands v0.12.8, gz lands v0.12.9, encryptPacked-wrap lands v0.12.10)");
213
+ "extract: format=" + JSON.stringify(format) + " — v0.12.9 ships ZIP + tar + tar.gz " +
214
+ "(encryptPacked-wrap lands v0.12.10)");
185
215
  }
186
- var reader = archiveRead().zip(source, {
187
- bombPolicy: opts.bombPolicy,
188
- entryTypePolicy: opts.entryTypePolicy,
189
- guardProfile: opts.guardProfile,
190
- audit: opts.audit,
216
+ var result = await reader.extract({
217
+ destination: opts.destination,
218
+ allowDangerous: opts.allowDangerous,
191
219
  });
192
- var result = await reader.extract({ destination: opts.destination });
193
220
  return Object.assign({ format: format }, result);
194
221
  } finally {
195
222
  if (typeof source.close === "function" && typeof opts.source === "string") {
@@ -238,14 +265,21 @@ async function inspect(opts) {
238
265
  var sniff = await _sniffMagic(source);
239
266
  format = sniff.format;
240
267
  }
241
- if (format !== "zip") {
268
+ var reader;
269
+ if (format === "zip") {
270
+ reader = archiveRead().zip(source, {
271
+ bombPolicy: opts.bombPolicy,
272
+ audit: opts.audit,
273
+ });
274
+ } else if (format === "tar") {
275
+ reader = archiveTarRead().tar(source, {
276
+ bombPolicy: opts.bombPolicy,
277
+ audit: opts.audit,
278
+ });
279
+ } else {
242
280
  throw new SafeArchiveError("safe-archive/format-unsupported",
243
- "inspect: format=" + JSON.stringify(format) + " — v0.12.7 ships ZIP only");
281
+ "inspect: format=" + JSON.stringify(format) + " — v0.12.8 ships ZIP + tar");
244
282
  }
245
- var reader = archiveRead().zip(source, {
246
- bombPolicy: opts.bombPolicy,
247
- audit: opts.audit,
248
- });
249
283
  var entries = await reader.inspect();
250
284
  var totalCompressed = 0;
251
285
  var totalUncompressed = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.12.7",
3
+ "version": "0.12.9",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:6ee9d122-fc29-4cdb-a4fb-bd4afab5f35d",
5
+ "serialNumber": "urn:uuid:f5a3cd7a-802d-4f55-ba59-958f474f38a0",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-23T15:14:39.191Z",
8
+ "timestamp": "2026-05-23T17:58:07.192Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.12.7",
22
+ "bom-ref": "@blamejs/core@0.12.9",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.12.7",
25
+ "version": "0.12.9",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.12.7",
29
+ "purl": "pkg:npm/%40blamejs/core@0.12.9",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.12.7",
57
+ "ref": "@blamejs/core@0.12.9",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]