fix(FN-3888): resolve dependency-graph plugin from src in dashboard build

The dashboard dynamically imports `@fusion-plugin-examples/dependency-graph/dashboard-view`, which resolved through the plugin's package.json exports to `dist/`. When plugin source was edited without rebuilding, stale `dist/` (extensionless ESM imports) made the import throw and the UI surfaced "Bundled plugin view unavailable". Add vite/vitest aliases mapping the plugin (and its `/dashboard-view` subpath) to `src/` so the dashboard never depends on `dist/`. Mirrors the existing pattern for hermes/openclaw/paperclip runtimes. Also extends the runtime-plugin alias regression test, and emits `.js` extensions from the plugin source for the CLI-bundled path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-09 16:44:44 -07:00
parent d942c0c404
commit 47504ea0c8
15 changed files with 90 additions and 25 deletions

View File

@@ -1,7 +1,8 @@
import { mkdtempSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { existsSync, mkdtempSync } from "node:fs";
import { rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { dirname, join, resolve } from "node:path";
import { PluginLoader, PluginStore } from "@fusion/core";
import { afterEach, describe, expect, it } from "vitest";
import plugin from "../index";
@@ -24,6 +25,7 @@ describe("dependency graph plugin index", () => {
);
});
// Vitest's resolver can mask extensionless-import issues in emitted dist files.
it("is loadable through package exports", async () => {
const entryModule = await import("@fusion-plugin-examples/dependency-graph");
expect(entryModule.default?.manifest?.id).toBe("fusion-plugin-dependency-graph");
@@ -35,6 +37,29 @@ describe("dependency graph plugin index", () => {
expect(typeof viewModule.default).toBe("function");
});
const hasNodeImportPrereqs =
existsSync(join(process.cwd(), "dist/dashboard-view.js")) &&
existsSync(join(process.cwd(), "node_modules/@fusion/plugin-sdk/dist/index.js"));
const nodeImportTest = hasNodeImportPrereqs ? it : it.skip;
nodeImportTest("keeps built entrypoint imports Node-ESM-safe for relative specifiers", () => {
const script =
"Promise.all([" +
"import('./plugins/fusion-plugin-dependency-graph/dist/index.js')," +
"import('node:fs/promises').then((fs) => fs.readFile('./plugins/fusion-plugin-dependency-graph/dist/dashboard-view.js', 'utf8'))" +
"]).then(([root, dashboardViewSource]) => {" +
"if (root.default?.manifest?.id !== 'fusion-plugin-dependency-graph') process.exit(2);" +
"if (!dashboardViewSource.includes('from \\\"./DependencyGraph.js\\\"')) process.exit(3);" +
"}).catch((e) => { console.error(e?.code, e?.message); process.exit(1); });";
const repoRoot = resolve(process.cwd(), "../..");
const result = spawnSync(process.execPath, ["-e", script], {
cwd: repoRoot,
encoding: "utf8",
});
expect(result.status, `Node import check failed: ${result.stderr || result.stdout}`).toBe(0);
});
it("is loadable by PluginLoader without throwing", async () => {
const rootDir = mkdtempSync(join(tmpdir(), "fn-3737-plugin-loader-"));
testDirs.push(rootDir);