plugin(telemetry-watcher): inline plugin SDK to break ESM TS chain

@fusion/plugin-sdk's package.json points its import entry at
src/index.ts (TS source), and that file imports core/src/plugin-types.js
via relative path. Node 22 ESM has no TS loader so the chain
unraveled at runtime: the plugin loader successfully resolved our
compiled dist/index.js, but the very first `import { definePlugin }
from "@fusion/plugin-sdk"` walked into TS source and fell over.

definePlugin is a pure typed identity function — it adds no runtime
behavior. Inline its source plus a structural copy of the plugin
context/route/manifest types we use, drop the @fusion/plugin-sdk
dependency entirely. The compiled output now imports nothing outside
node:crypto and Node builtins, so it can load anywhere fusion can
spawn ESM modules.

When fusion publishes a compiled SDK build, swap the inline types
back to import statements and restore the workspace dependency.
This commit is contained in:
Semih
2026-05-10 12:45:58 +00:00
parent 278d96df55
commit 10dca2d3da
3 changed files with 81 additions and 29 deletions

View File

@@ -23,13 +23,73 @@
*/
import { createHash } from "node:crypto";
import { definePlugin } from "@fusion/plugin-sdk";
import type {
FusionPlugin,
PluginContext,
PluginRouteDefinition,
PluginSettingSchema,
} from "@fusion/plugin-sdk";
// ── Inline plugin SDK surface ──────────────────────────────────────────────
//
// We deliberately do NOT import from "@fusion/plugin-sdk" at runtime. The
// SDK package has its `import` export entry pointing at a TypeScript file
// (src/index.ts) which itself imports more TS files from @fusion/core via
// relative paths. Node 22 ESM cannot resolve those without a TS loader at
// runtime, so the loader fails before our handler ever runs. The SDK
// surface we actually use is structural: definePlugin is a typed identity
// helper, and the type aliases are erased at compile time. Inlining these
// lets the compiled dist/index.js load without traversing into TS sources.
//
// When fusion publishes a compiled SDK build, swap these back to import
// statements and remove the local declarations.
interface PluginSettingSchema {
type: "string" | "number" | "boolean" | "enum" | "password" | "array";
label?: string;
description?: string;
defaultValue?: unknown;
required?: boolean;
enumValues?: string[];
multiline?: boolean;
itemType?: "string" | "number";
}
interface PluginLogger {
info(message: string, ...args: unknown[]): void;
warn(message: string, ...args: unknown[]): void;
error(message: string, ...args: unknown[]): void;
debug(message: string, ...args: unknown[]): void;
}
interface PluginContext {
pluginId: string;
taskStore: unknown;
settings: Record<string, unknown>;
logger: PluginLogger;
emitEvent: (event: string, data: unknown) => void;
}
interface PluginRouteDefinition {
method: "GET" | "POST" | "PUT" | "DELETE";
path: string;
description?: string;
handler: (req: unknown, ctx: PluginContext) => Promise<unknown>;
}
interface FusionPlugin {
manifest: {
id: string;
name: string;
version: string;
description?: string;
settingsSchema?: Record<string, PluginSettingSchema>;
};
state: "installed" | "started" | "stopped" | "error";
hooks?: {
onLoad?: (ctx: PluginContext) => Promise<void> | void;
onUnload?: () => Promise<void> | void;
};
routes?: PluginRouteDefinition[];
}
function definePlugin(plugin: FusionPlugin): FusionPlugin {
return plugin;
}
// ── Severity classifier ─────────────────────────────────────────────────────
@@ -532,7 +592,11 @@ const plugin: FusionPlugin = definePlugin({
);
try {
const task = await ctx.taskStore.createTask({
const taskStore = ctx.taskStore as {
createTask: (input: Record<string, unknown>) => Promise<{ id: string }>;
updateTask?: (id: string, patch: Record<string, unknown>) => Promise<unknown>;
};
const task = await taskStore.createTask({
title: `[${severity}] ${item.alertname}`,
description,
column: "triage",
@@ -545,20 +609,15 @@ const plugin: FusionPlugin = definePlugin({
} as never,
});
if (triageAgentId) {
const store = ctx.taskStore as unknown as {
updateTask?: (id: string, patch: Record<string, unknown>) => Promise<unknown>;
};
if (typeof store.updateTask === "function") {
try {
await store.updateTask(task.id, { assignedAgentId: triageAgentId });
} catch (assignErr) {
ctx.logger.warn(
`Could not auto-assign task ${task.id}: ${
assignErr instanceof Error ? assignErr.message : String(assignErr)
}`,
);
}
if (triageAgentId && typeof taskStore.updateTask === "function") {
try {
await taskStore.updateTask(task.id, { assignedAgentId: triageAgentId });
} catch (assignErr) {
ctx.logger.warn(
`Could not auto-assign task ${task.id}: ${
assignErr instanceof Error ? assignErr.message : String(assignErr)
}`,
);
}
}