feat(FN-3717): gate bootstrap on openclaw bridge asset

Stabilizes the OpenClaw bundle by hardening the asset copy logic and gating the bootstrap flow on the openclaw bridge asset being present, with regression tests covering the new bundle-output helpers and a patch changeset for `@runfusion/fusion`.

Fusion-Task-Id: FN-3717
This commit is contained in:
Fusion
2026-05-07 18:56:46 -07:00
committed by gsxdsm
parent c0ed278a4d
commit a732ebb8c9
8 changed files with 92 additions and 8 deletions

View File

@@ -0,0 +1,60 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const state = {
existingPaths: new Set<string>(),
indexHtml: "",
};
vi.mock("node:fs", () => ({
existsSync: (path: string) => state.existingPaths.has(path),
readFileSync: () => state.indexHtml,
execFileSync: vi.fn(),
execSync: vi.fn(),
}));
import {
bundlePath,
clientIndexPath,
dashboardClientStubMarker,
hasBuiltDashboardAssets,
openclawMcpSchemaServerPath,
} from "./bundle-output-helpers";
const cursorPluginManifestPath = bundlePath.replace(
"dist/bin.js",
"dist/plugins/fusion-plugin-cursor-runtime/manifest.json",
);
describe("hasBuiltDashboardAssets", () => {
beforeEach(() => {
state.existingPaths.clear();
state.indexHtml = "<html><body><script src=\"assets/app.js\"></script></body></html>";
});
it("returns false when openclaw mcp-schema-server.cjs is missing", () => {
state.existingPaths.add(bundlePath);
state.existingPaths.add(clientIndexPath);
state.existingPaths.add(cursorPluginManifestPath);
expect(hasBuiltDashboardAssets()).toBe(false);
});
it("returns true when all required assets exist and dashboard stub marker is absent", () => {
state.existingPaths.add(bundlePath);
state.existingPaths.add(clientIndexPath);
state.existingPaths.add(cursorPluginManifestPath);
state.existingPaths.add(openclawMcpSchemaServerPath);
expect(hasBuiltDashboardAssets()).toBe(true);
});
it("returns false when dashboard client index contains stub marker", () => {
state.existingPaths.add(bundlePath);
state.existingPaths.add(clientIndexPath);
state.existingPaths.add(cursorPluginManifestPath);
state.existingPaths.add(openclawMcpSchemaServerPath);
state.indexHtml = dashboardClientStubMarker;
expect(hasBuiltDashboardAssets()).toBe(false);
});
});

View File

@@ -7,6 +7,13 @@ export const workspaceRoot = join(cliRoot, "..", "..");
export const bundlePath = join(cliRoot, "dist", "bin.js");
export const clientIndexPath = join(cliRoot, "dist", "client", "index.html");
const cursorPluginManifestPath = join(cliRoot, "dist", "plugins", "fusion-plugin-cursor-runtime", "manifest.json");
export const openclawMcpSchemaServerPath = join(
cliRoot,
"dist",
"plugins",
"fusion-plugin-openclaw-runtime",
"mcp-schema-server.cjs",
);
export const dashboardClientStubMarker = "Dashboard assets not built";
@@ -28,8 +35,13 @@ function runBuildCommand(command: string, cwd: string) {
});
}
function hasBuiltDashboardAssets(): boolean {
if (!existsSync(bundlePath) || !existsSync(clientIndexPath) || !existsSync(cursorPluginManifestPath)) {
export function hasBuiltDashboardAssets(): boolean {
if (
!existsSync(bundlePath) ||
!existsSync(clientIndexPath) ||
!existsSync(cursorPluginManifestPath) ||
!existsSync(openclawMcpSchemaServerPath)
) {
return false;
}