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:
@@ -1,15 +1,15 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { Task } from "@fusion/core";
|
||||
import { GraphTaskNode } from "./GraphTaskNode";
|
||||
import { GraphToolbar } from "./GraphToolbar";
|
||||
import { GraphEdges } from "./edges";
|
||||
import { filterGraphTasks } from "./filters";
|
||||
import { computeAutoLayout } from "./layout";
|
||||
import { useGraphData } from "./useGraphData";
|
||||
import { useGraphInteraction } from "./useGraphInteraction";
|
||||
import { useDependencyChain } from "./hooks/useDependencyChain";
|
||||
import { useGraphPositions } from "./hooks/useGraphPositions";
|
||||
import { mergePositions, type NodePositions } from "./utils/graphPositionStorage";
|
||||
import { GraphTaskNode } from "./GraphTaskNode.js";
|
||||
import { GraphToolbar } from "./GraphToolbar.js";
|
||||
import { GraphEdges } from "./edges.js";
|
||||
import { filterGraphTasks } from "./filters.js";
|
||||
import { computeAutoLayout } from "./layout.js";
|
||||
import { useGraphData } from "./useGraphData.js";
|
||||
import { useGraphInteraction } from "./useGraphInteraction.js";
|
||||
import { useDependencyChain } from "./hooks/useDependencyChain.js";
|
||||
import { useGraphPositions } from "./hooks/useGraphPositions.js";
|
||||
import { mergePositions, type NodePositions } from "./utils/graphPositionStorage.js";
|
||||
import "./DependencyGraph.css";
|
||||
|
||||
const NODE_WIDTH = 280;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSProperties, ComponentProps, HTMLAttributes } from "react";
|
||||
import type { GraphPosition } from "./types";
|
||||
import { useNodeDrag } from "./hooks/useNodeDrag";
|
||||
import type { GraphPosition } from "./types.js";
|
||||
import { useNodeDrag } from "./hooks/useNodeDrag.js";
|
||||
import { TaskCard } from "@fusion/dashboard/app/components/TaskCard";
|
||||
import { isTaskStuck } from "@fusion/dashboard/app/utils/taskStuck";
|
||||
import "./GraphTaskNode.css";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Task, TaskDetail, WorkflowStep } from "@fusion/core";
|
||||
import type { PluginDashboardViewContext } from "@fusion/dashboard/app/plugins/types";
|
||||
import { createElement } from "react";
|
||||
import { DependencyGraph } from "./DependencyGraph";
|
||||
import { DependencyGraph } from "./DependencyGraph.js";
|
||||
|
||||
function createWorkflowStepNameLookup(workflowSteps: WorkflowStep[] | undefined): ReadonlyMap<string, string> {
|
||||
return new Map((workflowSteps ?? []).map((step) => [step.id, step.name] as const));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GraphEdge, GraphPosition } from "./types";
|
||||
import type { GraphEdge, GraphPosition } from "./types.js";
|
||||
import "./GraphHighlight.css";
|
||||
|
||||
interface GraphEdgesProps {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { clearPositions, loadPositions, savePositions, type NodePositions } from "../utils/graphPositionStorage";
|
||||
import { clearPositions, loadPositions, savePositions, type NodePositions } from "../utils/graphPositionStorage.js";
|
||||
|
||||
function filterVisiblePositions(positions: NodePositions, visibleTaskIds: Set<string>): NodePositions {
|
||||
const filtered: NodePositions = {};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import type { MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent } from "react";
|
||||
import type { GraphPosition } from "../types";
|
||||
import type { GraphPosition } from "../types.js";
|
||||
|
||||
const DRAG_THRESHOLD_PX = 4;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GraphData, GraphPosition } from "./types";
|
||||
import type { GraphData, GraphPosition } from "./types.js";
|
||||
|
||||
export interface LayoutOptions {
|
||||
nodeWidth?: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import type { Task } from "@fusion/core";
|
||||
import type { GraphData, GraphNode } from "./types";
|
||||
import type { GraphData, GraphNode } from "./types.js";
|
||||
|
||||
export function useGraphData(tasks: Task[]): GraphData {
|
||||
return useMemo(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { KeyboardEvent as ReactKeyboardEvent } from "react";
|
||||
import type { LayoutOptions } from "./layout";
|
||||
import type { GraphPosition } from "./types";
|
||||
import type { LayoutOptions } from "./layout.js";
|
||||
import type { GraphPosition } from "./types.js";
|
||||
|
||||
const MIN_ZOOM = 0.1;
|
||||
const MAX_ZOOM = 3;
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
"outDir": "dist",
|
||||
"rootDir": "./src",
|
||||
"jsx": "react-jsx",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["react"],
|
||||
"paths": {
|
||||
"@fusion/dashboard/app/components/TaskCard": ["./src/dashboard-interop.d.ts"],
|
||||
@@ -14,5 +12,5 @@
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"],
|
||||
"exclude": ["src/__tests__/**"]
|
||||
"exclude": ["src/**/__tests__/**"]
|
||||
}
|
||||
Reference in New Issue
Block a user