@hoardodile/host 0.1.0 → 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.
- package/dist/hoard/index.d.ts +21 -24
- package/dist/hoard/index.js +51 -43
- package/dist/hoard/index.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +52 -25
- package/dist/index.js.map +1 -1
- package/dist/pack-6IscrwYN.d.ts +35 -0
- package/package.json +3 -3
- package/src/archive/archive-ops.test.ts +49 -1
- package/src/archive/extract.ts +15 -0
- package/src/archive/index.ts +1 -1
- package/src/archive/pack.ts +50 -0
- package/src/hoard/paths.test.ts +1 -0
- package/src/hoard/paths.ts +9 -0
- package/src/index.ts +1 -0
- package/src/sandbox/fixtures/asset-plugin.mjs +8 -3
- package/src/sandbox/host.test.ts +26 -3
- package/src/sandbox/host.ts +15 -8
package/dist/hoard/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
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,
|
|
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 };
|
package/dist/hoard/index.js
CHANGED
|
@@ -573,16 +573,16 @@ function parseSltListing(stdout, legacyNames) {
|
|
|
573
573
|
}
|
|
574
574
|
continue;
|
|
575
575
|
}
|
|
576
|
-
const
|
|
577
|
-
if (
|
|
578
|
-
const key = line.slice(0,
|
|
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(
|
|
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(
|
|
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 =
|
|
1369
|
+
const vRoot = join6(versionsRootPath, vSeg);
|
|
1363
1370
|
return {
|
|
1364
1371
|
root: vRoot,
|
|
1365
1372
|
version,
|
|
1366
|
-
versionSnapshotDb: () =>
|
|
1367
|
-
resource: (id) =>
|
|
1368
|
-
resourceData: (id) =>
|
|
1369
|
-
resources: () =>
|
|
1370
|
-
characters: () =>
|
|
1371
|
-
documents: () =>
|
|
1372
|
-
character: (id) =>
|
|
1373
|
-
dbBackups: () =>
|
|
1374
|
-
dbBackup: (name) =>
|
|
1375
|
-
snapshots: () =>
|
|
1376
|
-
snapshot: (name) =>
|
|
1377
|
-
deletedMarker: (kind, id) =>
|
|
1378
|
-
document: (id) =>
|
|
1379
|
-
plugins: () =>
|
|
1380
|
-
pluginVaultDir: (id) =>
|
|
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 =
|
|
1386
|
-
const cacheRoot =
|
|
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: () =>
|
|
1391
|
-
localCover: (subjectKind, id, variant, format) =>
|
|
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) =>
|
|
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) =>
|
|
1405
|
-
resFilesCache: (id) =>
|
|
1406
|
-
resource: (id) =>
|
|
1407
|
-
character: (id) =>
|
|
1408
|
-
trash: () =>
|
|
1409
|
-
trashItem: (id) =>
|
|
1410
|
-
tmp: () =>
|
|
1411
|
-
tmpFile: (name) =>
|
|
1412
|
-
sessionKey: () =>
|
|
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: () =>
|
|
1415
|
-
stagingPoolFile: (fileId, ext) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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: () =>
|
|
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
|
|
1512
|
+
function join6(...segments) {
|
|
1505
1513
|
return resolve(...segments);
|
|
1506
1514
|
}
|
|
1507
1515
|
function assertInside(ancestor, candidate) {
|