summaryrefslogtreecommitdiffhomepage
path: root/packages/host-bin/src/load-external.ts
blob: 522cff863bab64b2d42c98528d83709f7947b56e (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";
}