@hoardodile/host 0.1.1 → 0.1.2

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.
@@ -1,4 +1,7 @@
1
- import { Readable } from 'node:stream';
1
+ export { Z as ZipStreamEntry, s as streamStoredZip } from '../pack-6IscrwYN.js';
2
+ import 'node:stream';
3
+
4
+ type ContainerFormat = "zip" | "tar" | "7z" | "rar" | "xz" | "gzip";
2
5
 
3
6
  /**
4
7
  * Whole-archive extraction. Every supported format (zip, tar, 7z, rar,
@@ -33,6 +36,14 @@ type ExtractArchiveOptions = {
33
36
  /** Optional entry-count budget (enforced on the listing). */
34
37
  readonly maxEntries?: number;
35
38
  readonly onProgress?: ZipExtractReporter;
39
+ /**
40
+ * Optional format allow-list (e.g. `["zip"]` for plugin installs).
41
+ * An archive sniffed outside the list is rejected up front with
42
+ * `resource.archive_format_not_allowed` — project-internal channels
43
+ * that only ever produce one format stop admitting the others
44
+ * instead of opening every codec to untrusted input.
45
+ */
46
+ readonly formats?: readonly ContainerFormat[];
36
47
  };
37
48
  /**
38
49
  * Stream an archive into `destDir`, format-agnostic: every supported
@@ -92,28 +103,6 @@ declare function listArchiveEntries(archivePath: string): Promise<readonly Archi
92
103
  */
93
104
  declare function validateArchiveBudget(archivePath: string, maxBytes: number): Promise<void>;
94
105
 
95
- /**
96
- * On-the-fly zip exports. Resource sources are stored as bare files on
97
- * disk — nothing is packed at commit time; this module streams STORED
98
- * zip bytes straight to the consumer without a staging file.
99
- */
100
- /**
101
- * A logical zip entry whose bytes are produced on demand. Streams are
102
- * read once, in order, when the output is consumed.
103
- */
104
- type ZipStreamEntry = {
105
- readonly name: string;
106
- readonly size: number;
107
- readonly openStream: () => Readable;
108
- };
109
- /**
110
- * Stream a STORED zip from an ordered list of logical entries. Used by
111
- * the HTTP layer for exports (resource source packs, bulk downloads)
112
- * without a staging file on disk. Zero-length entries are packed as
113
- * empty buffers.
114
- */
115
- declare function streamStoredZip(entries: readonly ZipStreamEntry[]): NodeJS.ReadableStream;
116
-
117
106
  /**
118
107
  * Shared directory-size walk: the one implementation of "recursive byte
119
108
  * size of a directory tree" for host storage. Consumers opt into
@@ -351,6 +340,14 @@ type LocalPaths = {
351
340
  * `local/` (never synced) so each host has its own seal key.
352
341
  */
353
342
  sessionKey(): string;
343
+ /**
344
+ * Path to the seed-removal marker file: `<root>/local/seed-removals.json`.
345
+ * Holds the plugin ids (UUIDs) whose bundled seed was deliberately
346
+ * uninstalled by this host, so boot-time seeding skips them until the
347
+ * user restores them. Host-only state — never synced, survives app
348
+ * updates, and stays out of the wipe-on-clear `cache/` tree.
349
+ */
350
+ seedRemovals(): string;
354
351
  /**
355
352
  * Root of the host-only temporary directory tree:
356
353
  * `<localRoot>/.tmp`. Holds the global staging pool
@@ -751,4 +748,4 @@ type VersionedWriteCommand<T> = (paths: StoragePaths["latest"]) => T | Promise<T
751
748
  */
752
749
  declare function writeVersioned<T>(paths: StoragePaths, readOnly: boolean, cmd: VersionedWriteCommand<T>): Promise<T>;
753
750
 
754
- export { type CreateNextVersionResult, type CreateStoragePathsOptions, type DirSizeOptions, type LocalPaths, ORDER_MANIFEST_NAME, type OccupiedNames, PluginVaultPathError, RESOURCE_DATA_DIR_NAME, type StoragePaths, type VaultCommitResult, type VersionPaths, type VersionedFolderOps, type VersionedWriteCommand, type ZipStreamEntry, archiveStaleFiles, assertInside, assertSafeSegment, buildVersionedFolderOps, commitVaultFile, createNextVersion, createOccupiedNames, createStoragePaths, currentVersion, discardVaultTempFile, ensureBootstrapVersion, extractArchiveInto, findStagedArchiveFile, findStagedPoolFile, imageVariantKey, listArchiveEntries, listVersions, naturalSort, occupyEntryName, orderEntries, orderManifestPath, parseOrderManifest, parsePluginVaultDest, readActiveVersion, readOrderManifest, removeStagedPoolFile, resolveStagedPoolFiles, sanitizeEntryName, streamStoredZip, sumDirSizes, uniqueEntryName, validateArchiveBudget, vaultFileSha256, vaultReadFile, vaultRemoveFile, vaultStatFile, vaultTempFile, vaultTotalSize, versionedDbFile, versionedPath, writeActiveVersion, writeOrderManifest, writeStagedArchiveFile, writeStagedPoolFile, writeVersioned };
751
+ export { type CreateNextVersionResult, type CreateStoragePathsOptions, type DirSizeOptions, type LocalPaths, ORDER_MANIFEST_NAME, type OccupiedNames, PluginVaultPathError, RESOURCE_DATA_DIR_NAME, type StoragePaths, type VaultCommitResult, type VersionPaths, type VersionedFolderOps, type VersionedWriteCommand, archiveStaleFiles, assertInside, assertSafeSegment, buildVersionedFolderOps, commitVaultFile, createNextVersion, createOccupiedNames, createStoragePaths, currentVersion, discardVaultTempFile, ensureBootstrapVersion, extractArchiveInto, findStagedArchiveFile, findStagedPoolFile, imageVariantKey, listArchiveEntries, listVersions, naturalSort, occupyEntryName, orderEntries, orderManifestPath, parseOrderManifest, parsePluginVaultDest, readActiveVersion, readOrderManifest, removeStagedPoolFile, resolveStagedPoolFiles, sanitizeEntryName, sumDirSizes, uniqueEntryName, validateArchiveBudget, vaultFileSha256, vaultReadFile, vaultRemoveFile, vaultStatFile, vaultTempFile, vaultTotalSize, versionedDbFile, versionedPath, writeActiveVersion, writeOrderManifest, writeStagedArchiveFile, writeStagedPoolFile, writeVersioned };
@@ -573,16 +573,16 @@ function parseSltListing(stdout, legacyNames) {
573
573
  }
574
574
  continue;
575
575
  }
576
- const sep3 = line.indexOf(" = ");
577
- if (sep3 < 0) continue;
578
- const key = line.slice(0, sep3);
576
+ const sep4 = line.indexOf(" = ");
577
+ if (sep4 < 0) continue;
578
+ const key = line.slice(0, sep4);
579
579
  if (key === "Path") {
580
- const raw = Buffer.from(line.slice(sep3 + 3), "latin1");
580
+ const raw = Buffer.from(line.slice(sep4 + 3), "latin1");
581
581
  const decoded = legacyNames ? decodeLegacyZipName(raw) : raw.toString("utf8");
582
582
  name = decoded.replace(/\\/g, "/");
583
583
  continue;
584
584
  }
585
- const value = line.slice(sep3 + 3);
585
+ const value = line.slice(sep4 + 3);
586
586
  switch (key) {
587
587
  case "Size":
588
588
  sizeBytes = Number.parseInt(value, 10) || 0;
@@ -694,6 +694,13 @@ async function extractArchiveInto(source, destDir, opts) {
694
694
  {}
695
695
  );
696
696
  }
697
+ if (opts.formats !== void 0 && !opts.formats.includes(format)) {
698
+ throw invalid(
699
+ "resource.archive_format_not_allowed",
700
+ `archive format ${format} is not allowed here (allowed: ${opts.formats.join(", ")})`,
701
+ { allowed: opts.formats }
702
+ );
703
+ }
697
704
  if (format === "zip" && resolveSevenZipPath() === void 0) {
698
705
  return extractZipBuffer(buffer2, destDir, opts);
699
706
  }
@@ -1359,65 +1366,66 @@ function createStoragePaths(opts) {
1359
1366
  }
1360
1367
  function versionAt(version) {
1361
1368
  const vSeg = assertSafeSegment(String(version));
1362
- const vRoot = join5(versionsRootPath, vSeg);
1369
+ const vRoot = join6(versionsRootPath, vSeg);
1363
1370
  return {
1364
1371
  root: vRoot,
1365
1372
  version,
1366
- versionSnapshotDb: () => join5(vRoot, "app.sqlite"),
1367
- resource: (id) => join5(vRoot, "resources", assertSafeSegment(id)),
1368
- resourceData: (id) => join5(vRoot, "resources", assertSafeSegment(id), RESOURCE_DATA_DIR_NAME),
1369
- resources: () => join5(vRoot, "resources"),
1370
- characters: () => join5(vRoot, "characters"),
1371
- documents: () => join5(vRoot, "documents"),
1372
- character: (id) => join5(vRoot, "characters", assertSafeSegment(id)),
1373
- dbBackups: () => join5(vRoot, "db-backups"),
1374
- dbBackup: (name) => join5(vRoot, "db-backups", assertSafeSegment(name)),
1375
- snapshots: () => join5(vRoot, "snapshots"),
1376
- snapshot: (name) => join5(vRoot, "snapshots", assertSafeSegment(name)),
1377
- deletedMarker: (kind, id) => join5(vRoot, kind, assertSafeSegment(id), ".deleted"),
1378
- document: (id) => join5(vRoot, "documents", assertSafeSegment(id)),
1379
- plugins: () => join5(vRoot, "plugins"),
1380
- pluginVaultDir: (id) => join5(vRoot, "plugins", assertSafeSegment(id), "vault")
1373
+ versionSnapshotDb: () => join6(vRoot, "app.sqlite"),
1374
+ resource: (id) => join6(vRoot, "resources", assertSafeSegment(id)),
1375
+ resourceData: (id) => join6(vRoot, "resources", assertSafeSegment(id), RESOURCE_DATA_DIR_NAME),
1376
+ resources: () => join6(vRoot, "resources"),
1377
+ characters: () => join6(vRoot, "characters"),
1378
+ documents: () => join6(vRoot, "documents"),
1379
+ character: (id) => join6(vRoot, "characters", assertSafeSegment(id)),
1380
+ dbBackups: () => join6(vRoot, "db-backups"),
1381
+ dbBackup: (name) => join6(vRoot, "db-backups", assertSafeSegment(name)),
1382
+ snapshots: () => join6(vRoot, "snapshots"),
1383
+ snapshot: (name) => join6(vRoot, "snapshots", assertSafeSegment(name)),
1384
+ deletedMarker: (kind, id) => join6(vRoot, kind, assertSafeSegment(id), ".deleted"),
1385
+ document: (id) => join6(vRoot, "documents", assertSafeSegment(id)),
1386
+ plugins: () => join6(vRoot, "plugins"),
1387
+ pluginVaultDir: (id) => join6(vRoot, "plugins", assertSafeSegment(id), "vault")
1381
1388
  };
1382
1389
  }
1383
1390
  const active = versionAt(activeVersion);
1384
1391
  const latest = versionAt(latestVersion);
1385
- const uploadStagingRootPath = join5(localRoot, ".tmp");
1386
- const cacheRoot = join5(localRoot, "cache");
1392
+ const uploadStagingRootPath = join6(localRoot, ".tmp");
1393
+ const cacheRoot = join6(localRoot, "cache");
1387
1394
  const local = {
1388
1395
  root: localRoot,
1389
1396
  cache: () => cacheRoot,
1390
- logs: () => join5(localRoot, "logs"),
1391
- localCover: (subjectKind, id, variant, format) => join5(
1397
+ logs: () => join6(localRoot, "logs"),
1398
+ localCover: (subjectKind, id, variant, format) => join6(
1392
1399
  cacheRoot,
1393
1400
  localCoverSubjectDir(subjectKind),
1394
1401
  assertSafeSegment(id),
1395
1402
  `${assertSafeSegment(variant)}.${format ?? "avif"}`
1396
1403
  ),
1397
- resFileVariant: (id, filename, variantKey, format) => join5(
1404
+ resFileVariant: (id, filename, variantKey, format) => join6(
1398
1405
  cacheRoot,
1399
1406
  "resources",
1400
1407
  assertSafeSegment(id),
1401
1408
  "file-preview",
1402
1409
  `${assertSafeSegment(sourceCacheId(filename))}__${assertSafeSegment(variantKey)}.${format ?? "avif"}`
1403
1410
  ),
1404
- resFilePreviewDir: (id) => join5(cacheRoot, "resources", assertSafeSegment(id), "file-preview"),
1405
- resFilesCache: (id) => join5(cacheRoot, "resources", assertSafeSegment(id), "files-cache.json"),
1406
- resource: (id) => join5(cacheRoot, "resources", assertSafeSegment(id)),
1407
- character: (id) => join5(cacheRoot, "characters", assertSafeSegment(id)),
1408
- trash: () => join5(localRoot, "trash"),
1409
- trashItem: (id) => join5(localRoot, "trash", assertSafeSegment(id)),
1410
- tmp: () => join5(cacheRoot, "tmp"),
1411
- tmpFile: (name) => join5(cacheRoot, "tmp", assertSafeSegment(name)),
1412
- sessionKey: () => join5(localRoot, ".session-key"),
1411
+ resFilePreviewDir: (id) => join6(cacheRoot, "resources", assertSafeSegment(id), "file-preview"),
1412
+ resFilesCache: (id) => join6(cacheRoot, "resources", assertSafeSegment(id), "files-cache.json"),
1413
+ resource: (id) => join6(cacheRoot, "resources", assertSafeSegment(id)),
1414
+ character: (id) => join6(cacheRoot, "characters", assertSafeSegment(id)),
1415
+ trash: () => join6(localRoot, "trash"),
1416
+ trashItem: (id) => join6(localRoot, "trash", assertSafeSegment(id)),
1417
+ tmp: () => join6(cacheRoot, "tmp"),
1418
+ tmpFile: (name) => join6(cacheRoot, "tmp", assertSafeSegment(name)),
1419
+ sessionKey: () => join6(localRoot, ".session-key"),
1420
+ seedRemovals: () => join6(localRoot, "seed-removals.json"),
1413
1421
  uploadStagingRoot: () => uploadStagingRootPath,
1414
- stagingPoolRoot: () => join5(uploadStagingRootPath, "staging"),
1415
- stagingPoolFile: (fileId, ext) => join5(
1422
+ stagingPoolRoot: () => join6(uploadStagingRootPath, "staging"),
1423
+ stagingPoolFile: (fileId, ext) => join6(
1416
1424
  uploadStagingRootPath,
1417
1425
  "staging",
1418
1426
  `${assertSafeSegment(fileId)}${ext}`
1419
1427
  ),
1420
- resVideoFrame: (id, filename, timeMs) => join5(
1428
+ resVideoFrame: (id, filename, timeMs) => join6(
1421
1429
  cacheRoot,
1422
1430
  "resources",
1423
1431
  assertSafeSegment(id),
@@ -1425,14 +1433,14 @@ function createStoragePaths(opts) {
1425
1433
  assertSafeSegment(sourceCacheId(filename)),
1426
1434
  `${timeMs}.avif`
1427
1435
  ),
1428
- resExtractedDir: (id, fileVersion) => join5(
1436
+ resExtractedDir: (id, fileVersion) => join6(
1429
1437
  cacheRoot,
1430
1438
  "resources",
1431
1439
  assertSafeSegment(id),
1432
1440
  "extracted",
1433
1441
  `v${fileVersion}`
1434
1442
  ),
1435
- resExtractedEntry: (id, fileVersion, entryName) => join5(
1443
+ resExtractedEntry: (id, fileVersion, entryName) => join6(
1436
1444
  cacheRoot,
1437
1445
  "resources",
1438
1446
  assertSafeSegment(id),
@@ -1440,7 +1448,7 @@ function createStoragePaths(opts) {
1440
1448
  `v${fileVersion}`,
1441
1449
  assertSafeSegment(sourceCacheId(entryName))
1442
1450
  ),
1443
- resExtractedArchivesDir: (id, fileVersion) => join5(
1451
+ resExtractedArchivesDir: (id, fileVersion) => join6(
1444
1452
  cacheRoot,
1445
1453
  "resources",
1446
1454
  assertSafeSegment(id),
@@ -1457,7 +1465,7 @@ function createStoragePaths(opts) {
1457
1465
  latest,
1458
1466
  local,
1459
1467
  atVersion: (v) => versionAt(v),
1460
- runtimeDb: () => join5(root, "app.sqlite")
1468
+ runtimeDb: () => join6(root, "app.sqlite")
1461
1469
  };
1462
1470
  }
1463
1471
  function localCoverSubjectDir(subjectKind) {
@@ -1501,7 +1509,7 @@ function assertSafeSegment(segment) {
1501
1509
  }
1502
1510
  return segment;
1503
1511
  }
1504
- function join5(...segments) {
1512
+ function join6(...segments) {
1505
1513
  return resolve(...segments);
1506
1514
  }
1507
1515
  function assertInside(ancestor, candidate) {