@hoardodile/host 0.1.1 → 0.1.3
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/chunks/worker-entry.mjs +6 -1
- package/dist/hoard/index.d.ts +44 -42
- package/dist/hoard/index.js +73 -47
- package/dist/hoard/index.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.js +126 -23
- package/dist/index.js.map +1 -1
- package/dist/pack-6IscrwYN.d.ts +35 -0
- package/package.json +2 -2
- 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/index.ts +4 -1
- package/src/hoard/paths.test.ts +1 -0
- package/src/hoard/paths.ts +46 -11
- package/src/hoard/versioned-folder-ops.ts +29 -6
- package/src/hooks.test.ts +70 -0
- package/src/hooks.ts +26 -0
- package/src/index.ts +1 -0
- package/src/install-api.ts +68 -0
- package/src/sandbox/host.ts +30 -1
- package/src/sandbox/worker-entry.mjs +6 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { I as ImageMetadataInput, b as ImageSourceProbe, A as AvProbeOptions, E
|
|
|
5
5
|
import { R as ResourceContainer } from './container-fKxefO7p.js';
|
|
6
6
|
import { P as PluginProbeCache } from './probe-cache-BkRI7HEK.js';
|
|
7
7
|
export { a as PLUGIN_PROBE_CACHE_MAX_ENTRIES, c as createProbeCache } from './probe-cache-BkRI7HEK.js';
|
|
8
|
+
export { p as packZipDirectory } from './pack-6IscrwYN.js';
|
|
8
9
|
import { PluginCapabilityKey } from '@hoardodile/sdk-types/plugin-capabilities';
|
|
9
10
|
import 'sharp';
|
|
10
11
|
|
|
@@ -684,6 +685,18 @@ type PluginHooks = {
|
|
|
684
685
|
* shape-validated (hex values, per-resource cap).
|
|
685
686
|
*/
|
|
686
687
|
readonly runImageHashes: (api: ResourceAPI, pluginId: PluginManifestId) => Promise<ImageHashesResult | undefined>;
|
|
688
|
+
/**
|
|
689
|
+
* Run the plugin's optional `onInstall` hook — best-effort, invoked
|
|
690
|
+
* by the host after a successful install/update commit (marketplace
|
|
691
|
+
* install/update or a zip upload; never seed/dev plugins). The hook
|
|
692
|
+
* receives an install-scoped {@link ResourceAPI}: no resource is
|
|
693
|
+
* attached (the file surface answers empty, `context.detect` is
|
|
694
|
+
* `undefined`), while the asset methods still work and stay gated by
|
|
695
|
+
* the shared consent dialog. A throwing (or consent-denied) hook is
|
|
696
|
+
* logged via `hookFailureText` and swallowed — the install itself is
|
|
697
|
+
* never failed; plugins must re-check at runtime.
|
|
698
|
+
*/
|
|
699
|
+
readonly runInstallHook: (pluginId: PluginManifestId) => Promise<void>;
|
|
687
700
|
};
|
|
688
701
|
declare function createPluginHooks(deps: PluginHooksDeps): PluginHooks;
|
|
689
702
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createWriteStream, createReadStream, existsSync, readFileSync, renameSync, rmSync, mkdirSync, readdirSync, cpSync, statSync, realpathSync } from 'fs';
|
|
2
|
-
import { dirname,
|
|
2
|
+
import { dirname, resolve, sep, isAbsolute, normalize, join } from 'path';
|
|
3
3
|
import { pluginAssetError, fileTypeFromName, createFailingPlugin, definePlugin } from '@hoardodile/sdk-types';
|
|
4
4
|
export { HOOK_NAMES, assertPluginShape, createFailingPlugin, createResourceAPIFixture, definePlugin, isDetected, isMissed, stubLogger } from '@hoardodile/sdk-types';
|
|
5
5
|
import { mimeToKind, MIME_FFMPEG_INPUT_FORMAT } from '@hoardodile/sdk-types/media-exts';
|
|
@@ -14,6 +14,7 @@ import { fork, spawn } from 'child_process';
|
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
15
|
import { buffer } from 'stream/consumers';
|
|
16
16
|
import { createInflateRaw } from 'zlib';
|
|
17
|
+
import yazl from 'yazl';
|
|
17
18
|
import { CAPABILITY_BY_METHOD } from '@hoardodile/sdk-types/plugin-capabilities';
|
|
18
19
|
import { pathToFileURL, fileURLToPath } from 'url';
|
|
19
20
|
import { pluginManifest } from '@hoardodile/sdk-types/schema';
|
|
@@ -661,16 +662,16 @@ function parseSltListing(stdout, legacyNames) {
|
|
|
661
662
|
}
|
|
662
663
|
continue;
|
|
663
664
|
}
|
|
664
|
-
const
|
|
665
|
-
if (
|
|
666
|
-
const key = line.slice(0,
|
|
665
|
+
const sep5 = line.indexOf(" = ");
|
|
666
|
+
if (sep5 < 0) continue;
|
|
667
|
+
const key = line.slice(0, sep5);
|
|
667
668
|
if (key === "Path") {
|
|
668
|
-
const raw = Buffer.from(line.slice(
|
|
669
|
+
const raw = Buffer.from(line.slice(sep5 + 3), "latin1");
|
|
669
670
|
const decoded = legacyNames ? decodeLegacyZipName(raw) : raw.toString("utf8");
|
|
670
671
|
name = decoded.replace(/\\/g, "/");
|
|
671
672
|
continue;
|
|
672
673
|
}
|
|
673
|
-
const value = line.slice(
|
|
674
|
+
const value = line.slice(sep5 + 3);
|
|
674
675
|
switch (key) {
|
|
675
676
|
case "Size":
|
|
676
677
|
sizeBytes = Number.parseInt(value, 10) || 0;
|
|
@@ -916,11 +917,11 @@ var globalMaterializeInflight = /* @__PURE__ */ new Map();
|
|
|
916
917
|
// src/archive/nested-entry.ts
|
|
917
918
|
var VIRTUAL_PATH_SEPARATOR = "!";
|
|
918
919
|
function splitVirtualPath(path) {
|
|
919
|
-
const
|
|
920
|
-
if (
|
|
921
|
-
const inner = path.slice(
|
|
920
|
+
const sep5 = path.indexOf(VIRTUAL_PATH_SEPARATOR);
|
|
921
|
+
if (sep5 <= 0) return void 0;
|
|
922
|
+
const inner = path.slice(sep5 + 1);
|
|
922
923
|
if (inner.length === 0) return void 0;
|
|
923
|
-
return { outer: path.slice(0,
|
|
924
|
+
return { outer: path.slice(0, sep5), inner };
|
|
924
925
|
}
|
|
925
926
|
function createOuterArchiveSource(outer, outerName, size) {
|
|
926
927
|
return {
|
|
@@ -972,7 +973,7 @@ function createNestedResolver(outer, opts = {}) {
|
|
|
972
973
|
zip: r
|
|
973
974
|
}));
|
|
974
975
|
}
|
|
975
|
-
async function
|
|
976
|
+
async function resolve6(path) {
|
|
976
977
|
const parts = splitVirtualPath(path);
|
|
977
978
|
if (parts === void 0) return { kind: "literal" };
|
|
978
979
|
const { outer: outerName, inner } = parts;
|
|
@@ -999,7 +1000,7 @@ function createNestedResolver(outer, opts = {}) {
|
|
|
999
1000
|
);
|
|
1000
1001
|
return work;
|
|
1001
1002
|
}
|
|
1002
|
-
return { resolve:
|
|
1003
|
+
return { resolve: resolve6, list };
|
|
1003
1004
|
}
|
|
1004
1005
|
function listedWithStream(outer, outerName, entry) {
|
|
1005
1006
|
return {
|
|
@@ -1321,13 +1322,40 @@ function createNestedCdCache(opts) {
|
|
|
1321
1322
|
}
|
|
1322
1323
|
return { get, set, clear };
|
|
1323
1324
|
}
|
|
1325
|
+
async function packZipDirectory(srcDir, zipPath) {
|
|
1326
|
+
const root = resolve(srcDir);
|
|
1327
|
+
const files = listFilesSorted(root);
|
|
1328
|
+
const zip = new yazl.ZipFile();
|
|
1329
|
+
for (const abs of files) {
|
|
1330
|
+
const rel = abs.slice(root.length + 1).split(sep).join("/");
|
|
1331
|
+
zip.addFile(abs, rel, { compress: true });
|
|
1332
|
+
}
|
|
1333
|
+
zip.end();
|
|
1334
|
+
await pipeline(zip.outputStream, createWriteStream(resolve(zipPath)));
|
|
1335
|
+
}
|
|
1336
|
+
function listFilesSorted(root) {
|
|
1337
|
+
const out = [];
|
|
1338
|
+
const walk = (dir) => {
|
|
1339
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1340
|
+
const abs = join(dir, entry.name);
|
|
1341
|
+
if (entry.isDirectory()) {
|
|
1342
|
+
walk(abs);
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
if (!entry.isFile()) continue;
|
|
1346
|
+
out.push(abs);
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
walk(root);
|
|
1350
|
+
return out.sort();
|
|
1351
|
+
}
|
|
1324
1352
|
var PHASH_GRID = 32;
|
|
1325
1353
|
var MIN_PERCEPTUAL_STDDEV = 8;
|
|
1326
1354
|
function hashStream(stream, algo) {
|
|
1327
|
-
return new Promise((
|
|
1355
|
+
return new Promise((resolve6, reject) => {
|
|
1328
1356
|
const hash = createHash(algo);
|
|
1329
1357
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
1330
|
-
stream.on("end", () =>
|
|
1358
|
+
stream.on("end", () => resolve6(hash.digest("hex")));
|
|
1331
1359
|
stream.on("error", reject);
|
|
1332
1360
|
});
|
|
1333
1361
|
}
|
|
@@ -1456,14 +1484,14 @@ function createNestedAwareContainer(base, nestedCdCache, scope, extractCacheDir)
|
|
|
1456
1484
|
sizeBytes: entry.sizeBytes
|
|
1457
1485
|
};
|
|
1458
1486
|
}
|
|
1459
|
-
async function
|
|
1487
|
+
async function resolve6(rel) {
|
|
1460
1488
|
const nested = await resolver.resolve(rel);
|
|
1461
1489
|
if (nested.kind === "container") return nested;
|
|
1462
1490
|
const materialized = await resolveMaterialized(rel);
|
|
1463
1491
|
return materialized ?? nested;
|
|
1464
1492
|
}
|
|
1465
1493
|
async function readEntrySlice(rel, start, end) {
|
|
1466
|
-
const resolved = await
|
|
1494
|
+
const resolved = await resolve6(rel);
|
|
1467
1495
|
if (resolved.kind === "literal") {
|
|
1468
1496
|
return base.readEntrySlice(rel, start, end);
|
|
1469
1497
|
}
|
|
@@ -1473,7 +1501,7 @@ function createNestedAwareContainer(base, nestedCdCache, scope, extractCacheDir)
|
|
|
1473
1501
|
return readVirtualSlice(() => resolved.openStream(), start, end);
|
|
1474
1502
|
}
|
|
1475
1503
|
async function openEntryStream(rel) {
|
|
1476
|
-
const resolved = await
|
|
1504
|
+
const resolved = await resolve6(rel);
|
|
1477
1505
|
if (resolved.kind === "literal") {
|
|
1478
1506
|
return base.openEntryStream(rel);
|
|
1479
1507
|
}
|
|
@@ -1487,7 +1515,7 @@ function createNestedAwareContainer(base, nestedCdCache, scope, extractCacheDir)
|
|
|
1487
1515
|
return { stream: resolved.openStream(), size: resolved.entry.sizeBytes };
|
|
1488
1516
|
}
|
|
1489
1517
|
async function readEntry(rel) {
|
|
1490
|
-
const resolved = await
|
|
1518
|
+
const resolved = await resolve6(rel);
|
|
1491
1519
|
if (resolved.kind === "literal") {
|
|
1492
1520
|
return base.readEntry(rel);
|
|
1493
1521
|
}
|
|
@@ -1497,7 +1525,7 @@ function createNestedAwareContainer(base, nestedCdCache, scope, extractCacheDir)
|
|
|
1497
1525
|
return buffer(resolved.openStream());
|
|
1498
1526
|
}
|
|
1499
1527
|
async function resolveByteRange(rel) {
|
|
1500
|
-
const resolved = await
|
|
1528
|
+
const resolved = await resolve6(rel);
|
|
1501
1529
|
if (resolved.kind === "literal") {
|
|
1502
1530
|
return base.resolveByteRange(rel);
|
|
1503
1531
|
}
|
|
@@ -1507,7 +1535,7 @@ function createNestedAwareContainer(base, nestedCdCache, scope, extractCacheDir)
|
|
|
1507
1535
|
return { size: resolved.entry.sizeBytes };
|
|
1508
1536
|
}
|
|
1509
1537
|
async function resolveSeekablePath(rel) {
|
|
1510
|
-
const resolved = await
|
|
1538
|
+
const resolved = await resolve6(rel);
|
|
1511
1539
|
if (resolved.kind === "literal") {
|
|
1512
1540
|
return base.resolveSeekablePath?.(rel);
|
|
1513
1541
|
}
|
|
@@ -2270,6 +2298,59 @@ function parseManifest(dirPath, dirName) {
|
|
|
2270
2298
|
}
|
|
2271
2299
|
return result.data;
|
|
2272
2300
|
}
|
|
2301
|
+
function createInstallScopeApi() {
|
|
2302
|
+
const noResource = (method) => new Error(
|
|
2303
|
+
`${method}() \u2014 no resource is attached to the onInstall hook; use the asset methods (download/statAsset/readAsset/deleteAsset) for install-time work`
|
|
2304
|
+
);
|
|
2305
|
+
return {
|
|
2306
|
+
logInfo() {
|
|
2307
|
+
},
|
|
2308
|
+
logWarn() {
|
|
2309
|
+
},
|
|
2310
|
+
logError() {
|
|
2311
|
+
},
|
|
2312
|
+
context: { detect: void 0 },
|
|
2313
|
+
listFileNames: async () => [],
|
|
2314
|
+
readFile: async () => {
|
|
2315
|
+
throw noResource("readFile");
|
|
2316
|
+
},
|
|
2317
|
+
statFile: async () => void 0,
|
|
2318
|
+
statFiles: async (paths) => paths.map(() => void 0),
|
|
2319
|
+
sniff: async () => void 0,
|
|
2320
|
+
probe: async () => ({ kind: "unknown", reason: "unavailable" }),
|
|
2321
|
+
hashBytes: async () => {
|
|
2322
|
+
throw noResource("hashBytes");
|
|
2323
|
+
},
|
|
2324
|
+
computeImageHashes: async () => void 0,
|
|
2325
|
+
listContainer: async () => {
|
|
2326
|
+
throw noResource("listContainer");
|
|
2327
|
+
},
|
|
2328
|
+
extractArchive: async () => {
|
|
2329
|
+
throw noResource("extractArchive");
|
|
2330
|
+
},
|
|
2331
|
+
download: async () => {
|
|
2332
|
+
throw pluginAssetError(
|
|
2333
|
+
"UNAVAILABLE",
|
|
2334
|
+
"download() \u2014 this host has no plugin asset service; only the app server host can download into the plugin vault"
|
|
2335
|
+
);
|
|
2336
|
+
},
|
|
2337
|
+
statAsset: async () => {
|
|
2338
|
+
throw unavailableAsset2("statAsset");
|
|
2339
|
+
},
|
|
2340
|
+
readAsset: async () => {
|
|
2341
|
+
throw unavailableAsset2("readAsset");
|
|
2342
|
+
},
|
|
2343
|
+
deleteAsset: async () => {
|
|
2344
|
+
throw unavailableAsset2("deleteAsset");
|
|
2345
|
+
}
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
function unavailableAsset2(method) {
|
|
2349
|
+
return pluginAssetError(
|
|
2350
|
+
"UNAVAILABLE",
|
|
2351
|
+
`${method}() \u2014 this host has no plugin asset vault; only the app server host manages vault files`
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2273
2354
|
|
|
2274
2355
|
// src/hooks.ts
|
|
2275
2356
|
var MAX_IMAGE_HASHES_PER_RESOURCE = 2e3;
|
|
@@ -2444,6 +2525,17 @@ function createPluginHooks(deps) {
|
|
|
2444
2525
|
pluginId
|
|
2445
2526
|
);
|
|
2446
2527
|
}
|
|
2528
|
+
async function runInstallHook(pluginId) {
|
|
2529
|
+
const entry = getRegistry().getById(pluginId);
|
|
2530
|
+
if (entry === void 0) return;
|
|
2531
|
+
await invokeHook(
|
|
2532
|
+
entry,
|
|
2533
|
+
entry.plugin.onInstall,
|
|
2534
|
+
createInstallScopeApi(),
|
|
2535
|
+
"onInstall",
|
|
2536
|
+
"warn"
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2447
2539
|
return {
|
|
2448
2540
|
defaultPluginId,
|
|
2449
2541
|
getEffectiveEntry,
|
|
@@ -2455,7 +2547,8 @@ function createPluginHooks(deps) {
|
|
|
2455
2547
|
resolveLocalCoverSource,
|
|
2456
2548
|
runMetaHooks,
|
|
2457
2549
|
supportsImageHashes,
|
|
2458
|
-
runImageHashes
|
|
2550
|
+
runImageHashes,
|
|
2551
|
+
runInstallHook
|
|
2459
2552
|
};
|
|
2460
2553
|
}
|
|
2461
2554
|
var HEX_VALUE = /^[0-9a-f]+$/;
|
|
@@ -2664,7 +2757,7 @@ function createPluginSandbox(config = DEFAULT_SANDBOX_CONFIG) {
|
|
|
2664
2757
|
await teardownChild(previous);
|
|
2665
2758
|
}
|
|
2666
2759
|
if (!opts.eager) {
|
|
2667
|
-
|
|
2760
|
+
await teardownChild(state);
|
|
2668
2761
|
}
|
|
2669
2762
|
return createSandboxedPlugin(
|
|
2670
2763
|
state.hooks ?? ["detect"],
|
|
@@ -2848,6 +2941,16 @@ function createPluginSandbox(config = DEFAULT_SANDBOX_CONFIG) {
|
|
|
2848
2941
|
if (child === void 0) {
|
|
2849
2942
|
throw new Error(`plugin ${state.id} sandbox unavailable`);
|
|
2850
2943
|
}
|
|
2944
|
+
try {
|
|
2945
|
+
return await invokeOnChild(state, child, hook, api);
|
|
2946
|
+
} finally {
|
|
2947
|
+
if (hook === "onInstall") {
|
|
2948
|
+
state.respawnTimes = [];
|
|
2949
|
+
await teardownChild(state);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
function invokeOnChild(state, child, hook, api) {
|
|
2851
2954
|
const callId = nextCallId++;
|
|
2852
2955
|
return new Promise((resolveCall, reject) => {
|
|
2853
2956
|
const call = {
|
|
@@ -3305,6 +3408,6 @@ function createProbeCache(maxEntries = PLUGIN_PROBE_CACHE_MAX_ENTRIES) {
|
|
|
3305
3408
|
};
|
|
3306
3409
|
}
|
|
3307
3410
|
|
|
3308
|
-
export { DEFAULT_PLUGIN_EXTRACT_MAX_BYTES, DEFAULT_PLUGIN_EXTRACT_MAX_ENTRIES, DEFAULT_SANDBOX_CONFIG, DomainError, MIN_PERCEPTUAL_STDDEV, PERCEPTUAL_HASH_KINDS, PHASH_GRID, PLUGIN_HOOK_HARD_TIMEOUT_MS, PLUGIN_MAX_API_CALLS_PER_HOOK, PLUGIN_MAX_LOGS_PER_HOOK, PLUGIN_MAX_RESULT_BYTES, PLUGIN_PROBE_CACHE_MAX_ENTRIES, PLUGIN_WATCHDOG_TIMEOUT_MS, PLUGIN_WORKER_MAX_OLD_SPACE_MB, PLUGIN_WORKER_MAX_RESPAWNS, PLUGIN_WORKER_RESPAWN_WINDOW_MS, buildRegistry, computeDHash, computePHash, conflict, createCapabilityGuard, createDirectoryContainer, createDirectoryResourceAPI, createDirectoryResourceAPI as createImportResourceAPI, createNestedAwareContainer, createNestedCdCache, createPluginActivation, createPluginDiscovery, createPluginHooks, createPluginLoader, createPluginResourceAPI, createPluginSandbox, createProbeCache, grayStddev, invalid, listZipEntries, materializeFile, notFound, parseManifest, resolveSafeImportPath, runPluginHook, seedPlugins };
|
|
3411
|
+
export { DEFAULT_PLUGIN_EXTRACT_MAX_BYTES, DEFAULT_PLUGIN_EXTRACT_MAX_ENTRIES, DEFAULT_SANDBOX_CONFIG, DomainError, MIN_PERCEPTUAL_STDDEV, PERCEPTUAL_HASH_KINDS, PHASH_GRID, PLUGIN_HOOK_HARD_TIMEOUT_MS, PLUGIN_MAX_API_CALLS_PER_HOOK, PLUGIN_MAX_LOGS_PER_HOOK, PLUGIN_MAX_RESULT_BYTES, PLUGIN_PROBE_CACHE_MAX_ENTRIES, PLUGIN_WATCHDOG_TIMEOUT_MS, PLUGIN_WORKER_MAX_OLD_SPACE_MB, PLUGIN_WORKER_MAX_RESPAWNS, PLUGIN_WORKER_RESPAWN_WINDOW_MS, buildRegistry, computeDHash, computePHash, conflict, createCapabilityGuard, createDirectoryContainer, createDirectoryResourceAPI, createDirectoryResourceAPI as createImportResourceAPI, createNestedAwareContainer, createNestedCdCache, createPluginActivation, createPluginDiscovery, createPluginHooks, createPluginLoader, createPluginResourceAPI, createPluginSandbox, createProbeCache, grayStddev, invalid, listZipEntries, materializeFile, notFound, packZipDirectory, parseManifest, resolveSafeImportPath, runPluginHook, seedPlugins };
|
|
3309
3412
|
//# sourceMappingURL=index.js.map
|
|
3310
3413
|
//# sourceMappingURL=index.js.map
|