blob: 34b8bce70143700985f4d730f5da7694268287e1 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
import type { Extension, Logger } from "@dispatch/kernel";
/**
* Load external (out-of-repo) extensions by dynamic import.
*
* Each specifier is a module the host imports at boot: an absolute path to a
* built/source entry, or a package name resolvable from the host's working
* directory. The module must expose an `Extension` as `export const extension`
* (or as its default export). The host's own loader (`createHost`) then applies
* apiVersion gating, dependency ordering, the capability gate, and per-extension
* fault isolation — external extensions get exactly the same treatment as
* bundled ones.
*
* Fault-isolated by design: a specifier that fails to import or exports no valid
* extension is logged and SKIPPED — a broken external extension must never crash
* boot (defend faults, not adversaries; never leave the system broken).
*/
export async function loadExternalExtensions(
specifiers: readonly string[],
logger: Logger,
): Promise<Extension[]> {
const loaded: Extension[] = [];
for (const spec of specifiers) {
try {
const mod = (await import(spec)) as Record<string, unknown>;
const candidate = mod.extension ?? mod.default ?? mod;
if (isExtension(candidate)) {
loaded.push(candidate);
logger.info(`Loaded external extension "${candidate.manifest.id}" from ${spec}`);
} else {
logger.warn(`External module "${spec}" has no valid extension export; skipped`);
}
} catch (err) {
logger.error(`Failed to load external extension "${spec}"; skipped`, { err });
}
}
return loaded;
}
/** Structural check that a dynamically-imported value is an `Extension`. */
function isExtension(value: unknown): value is Extension {
if (!value || typeof value !== "object") return false;
const e = value as { manifest?: unknown; activate?: unknown };
if (typeof e.activate !== "function") return false;
const m = e.manifest as { id?: unknown } | undefined;
return !!m && typeof m.id === "string";
}
|