diff --git a/.changeset/calm-todos-build.md b/.changeset/calm-todos-build.md new file mode 100644 index 0000000000..128aff2a56 --- /dev/null +++ b/.changeset/calm-todos-build.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Restore clean CLI packaging for the bundled Todo Lists plugin. +category: fix +dev: Re-exports AgentStore through the bundled plugin core runtime shim. \ No newline at end of file diff --git a/packages/cli/src/__tests__/plugin-sdk-export.test.ts b/packages/cli/src/__tests__/plugin-sdk-export.test.ts index 635b204b2e..b6bc830a1a 100644 --- a/packages/cli/src/__tests__/plugin-sdk-export.test.ts +++ b/packages/cli/src/__tests__/plugin-sdk-export.test.ts @@ -1,11 +1,39 @@ import { describe, it, expect } from "vitest"; import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; +import ts from "typescript"; import { definePlugin, validatePluginManifest } from "@fusion/plugin-sdk"; import { applyPrepackTransform } from "../../scripts/prepare-publish-manifest.mjs"; const workspaceRoot = join(__dirname, "..", "..", "..", ".."); +function executableModuleSpecifiers(source: string): string[] { + const sourceFile = ts.createSourceFile("artifact.js", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS); + const specifiers: string[] = []; + + const visit = (node: ts.Node): void => { + if ( + (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && + node.moduleSpecifier && + ts.isStringLiteralLike(node.moduleSpecifier) + ) { + specifiers.push(node.moduleSpecifier.text); + } else if ( + ts.isCallExpression(node) && + (node.expression.kind === ts.SyntaxKind.ImportKeyword || + (ts.isIdentifier(node.expression) && node.expression.text === "require")) && + node.arguments.length === 1 && + ts.isStringLiteralLike(node.arguments[0]) + ) { + specifiers.push(node.arguments[0].text); + } + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + return specifiers; +} + describe("plugin-sdk export surface", () => { it("keeps definePlugin as identity and validates manifests", () => { const plugin = { manifest: { id: "demo-plugin", name: "Demo", version: "1.0.0" } } as any; @@ -43,7 +71,7 @@ describe("plugin-sdk export surface", () => { expect(tsupRaw).toContain("/^@fusion\\//"); }); - it("uses a runtime-only core shim that bundles schema source and Quality supervision without core dist", () => { + it("uses a runtime-only core shim that bundles required plugin values without core dist", () => { const tsupPath = join(workspaceRoot, "packages", "cli", "tsup.config.ts"); const tsupRaw = readFileSync(tsupPath, "utf-8"); const shimPath = join(workspaceRoot, "packages", "cli", "src", "plugin-sdk-core-runtime-shim.mjs"); @@ -51,6 +79,12 @@ describe("plugin-sdk export surface", () => { expect(tsupRaw).toContain('"plugin-sdk-core-runtime-shim.mjs"'); expect(shimRaw).toContain('from "../../core/src/postgres/schema/index.js"'); + /* + * FNXC:BundledPlugins 2026-08-03-18:39: + * Bundled plugins must receive runtime values through the CLI source shim so clean package builds never leave private `@fusion/core` imports unresolved. + */ + expect(shimRaw).toContain('from "../../core/src/agents/agent-store.js"'); + expect(shimRaw).toContain("export { AgentStore, postgresSchema }"); expect(shimRaw).toContain("export function superviseSpawn"); expect(shimRaw).not.toContain("../../core/dist/"); }); @@ -61,7 +95,27 @@ describe("plugin-sdk export surface", () => { return; } const built = readFileSync(distPath, "utf-8"); - expect(built.includes("@fusion/")).toBe(false); + const fusionRuntimeSpecifiers = executableModuleSpecifiers(built).filter((specifier) => + specifier.startsWith("@fusion/"), + ); + expect(fusionRuntimeSpecifiers).toEqual([]); + }); + + it("distinguishes executable @fusion specifiers from documentation text", () => { + const source = [ + 'import value from "@fusion/static";', + 'import /* comment */ ("@fusion/dynamic");', + 'require(/* comment */ "@fusion/commonjs");', + 'export {} from /* comment */ "@fusion/exported";', + 'const docs = "Run pnpm --filter @fusion/core test";', + ].join("\n"); + + expect(executableModuleSpecifiers(source)).toEqual([ + "@fusion/static", + "@fusion/dynamic", + "@fusion/commonjs", + "@fusion/exported", + ]); }); it("has no @fusion specifiers in built plugin-sdk declaration artifact when present", () => { diff --git a/packages/cli/src/plugin-sdk-core-runtime-shim.mjs b/packages/cli/src/plugin-sdk-core-runtime-shim.mjs index edfaea48ab..e069974c5f 100644 --- a/packages/cli/src/plugin-sdk-core-runtime-shim.mjs +++ b/packages/cli/src/plugin-sdk-core-runtime-shim.mjs @@ -9,8 +9,13 @@ import { spawn } from "node:child_process"; * and bundles every runtime export without a private @fusion/core dependency. */ import * as postgresSchema from "../../core/src/postgres/schema/index.js"; +import { AgentStore } from "../../core/src/agents/agent-store.js"; -export { postgresSchema }; +/* + * FNXC:BundledPlugins 2026-08-03-17:18: + * The bundled Todo plugin lists project agents through AgentStore. Re-export the source implementation from the runtime shim so clean CLI packaging does not leave a private @fusion/core runtime import unresolved. + */ +export { AgentStore, postgresSchema }; /* * FNXC:BundledPlugins 2026-07-31-09:55: diff --git a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx index eda80af41a..ded3b3dd61 100644 --- a/packages/dashboard/app/components/__tests__/navigation-history.test.tsx +++ b/packages/dashboard/app/components/__tests__/navigation-history.test.tsx @@ -57,7 +57,18 @@ vi.mock("../../api", async (importOriginal) => { fetchAgents: vi.fn(() => Promise.resolve([])), fetchTaskDetail: vi.fn((id: string) => Promise.resolve({ id, title: `Task ${id}` })), fetchUnreadCount: vi.fn(() => Promise.resolve({ unreadCount: 0 })), - fetchPluginDashboardViews: vi.fn(() => Promise.resolve([])), + // FNXC:TodoNavigation 2026-08-03-17:18: Todo Lists is plugin-owned; keep this fixture production-shaped so the overflow destination uses the runtime manifest contribution. + fetchPluginDashboardViews: vi.fn(() => Promise.resolve([{ + pluginId: "fusion-plugin-todos", + view: { + viewId: "todos", + label: "Todos", + componentPath: "./dashboard-view", + icon: "CheckSquare", + placement: "overflow", + order: 70, + }, + }])), fetchExecutorStats: vi.fn(() => Promise.resolve({ globalPause: false, enginePaused: false, @@ -554,16 +565,16 @@ describe("Navigation history integration", () => { const pushCallsBefore = (window.history.pushState as any).mock.calls.length; fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger")); - fireEvent.click(screen.getByTestId("view-overflow-todos")); + fireEvent.click(await screen.findByTestId("view-overflow-plugin-fusion-plugin-todos-todos")); await waitFor(() => { - expect(screen.getByTestId("todo-view")).toBeTruthy(); + expect(screen.getByTestId("todo-view-root")).toBeTruthy(); }); expect((window.history.pushState as any).mock.calls.length).toBeGreaterThan(pushCallsBefore); dispatchPopState({ navIndex: 0 }); await waitFor(() => { - expect(screen.queryByTestId("todo-view")).toBeNull(); + expect(screen.queryByTestId("todo-view-root")).toBeNull(); expect(screen.getByTestId("board-view")).toBeTruthy(); }); }); diff --git a/plugins/fusion-plugin-todos/tsconfig.json b/plugins/fusion-plugin-todos/tsconfig.json index 041bbfdb36..e3f3129075 100644 --- a/plugins/fusion-plugin-todos/tsconfig.json +++ b/plugins/fusion-plugin-todos/tsconfig.json @@ -11,8 +11,7 @@ }, "types": [ "node", - "vitest/globals", - "@testing-library/jest-dom" + "vitest/globals" ] }, "include": [