summaryrefslogtreecommitdiffhomepage
path: root/packages/host-bin/src/load-external.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-10 09:51:00 +0900
committerAdam Malczewski <[email protected]>2026-06-10 09:51:00 +0900
commita980aba97aed34692dd655e716747c4ff53fb186 (patch)
tree56b32ed9db8f66f9f6141eda923f5d1f7c1ba6d9 /packages/host-bin/src/load-external.test.ts
parent52ce1bb8b25cc6ba4ba7d2734c35c95e0a08d723 (diff)
downloaddispatch-a980aba97aed34692dd655e716747c4ff53fb186.tar.gz
dispatch-a980aba97aed34692dd655e716747c4ff53fb186.zip
host-bin: external-extension loader + claude credential wiring
Add loadExternalExtensions(): fault-isolated dynamic import of out-of-repo extensions declared via DISPATCH_EXTERNAL_EXTENSIONS. main.ts assembles the credential-store in boot() so a 'claude' credential is registered when an external anthropic provider is loaded; config.ts surfaces the anthropic model / credential-key settings those extensions read.
Diffstat (limited to 'packages/host-bin/src/load-external.test.ts')
-rw-r--r--packages/host-bin/src/load-external.test.ts47
1 files changed, 47 insertions, 0 deletions
diff --git a/packages/host-bin/src/load-external.test.ts b/packages/host-bin/src/load-external.test.ts
new file mode 100644
index 0000000..38e3622
--- /dev/null
+++ b/packages/host-bin/src/load-external.test.ts
@@ -0,0 +1,47 @@
+import type { Logger } from "@dispatch/kernel";
+import { describe, expect, it } from "vitest";
+import { loadExternalExtensions } from "./load-external.js";
+
+function makeLogger(): Logger {
+ const noop = () => {};
+ const logger = {
+ debug: noop,
+ info: noop,
+ warn: noop,
+ error: noop,
+ child: () => logger,
+ span: () => {
+ throw new Error("not used");
+ },
+ } as unknown as Logger;
+ return logger;
+}
+
+describe("loadExternalExtensions", () => {
+ it("returns an empty array for no specifiers", async () => {
+ expect(await loadExternalExtensions([], makeLogger())).toEqual([]);
+ });
+
+ it("loads a real extension module by package name", async () => {
+ // auth-apikey is a bundled extension that exports `extension`; we use it as
+ // a stand-in for an external module to exercise the dynamic-import path.
+ const loaded = await loadExternalExtensions(["@dispatch/auth-apikey"], makeLogger());
+ expect(loaded).toHaveLength(1);
+ expect(loaded[0]?.manifest.id).toBe("auth-apikey");
+ });
+
+ it("skips a specifier that cannot be imported without throwing", async () => {
+ const loaded = await loadExternalExtensions(
+ ["./does-not-exist-xyz.js", "@dispatch/auth-apikey"],
+ makeLogger(),
+ );
+ // The bad one is skipped; the good one still loads.
+ expect(loaded.map((e) => e.manifest.id)).toEqual(["auth-apikey"]);
+ });
+
+ it("skips a module that exports no valid extension", async () => {
+ // `@dispatch/journal-sink` exports factories but no `extension`.
+ const loaded = await loadExternalExtensions(["@dispatch/journal-sink"], makeLogger());
+ expect(loaded).toEqual([]);
+ });
+});