feat(FN-3170): harden plugin bundling contract and fix verification regress
Hardened the roadmap plugin bundling contract with new output-validation tests and tsup config cleanup, then resolved regressions those changes introduced in the bundle and package-config test suites. A small dashboard UI tweak (TaskDetailModal) was also included. Fusion-Task-Id: FN-3170
This commit is contained in:
@@ -25,6 +25,10 @@ const cursorPluginManifestPath = bundlePath.replace(
|
||||
"dist/bin.js",
|
||||
"dist/plugins/fusion-plugin-cursor-runtime/manifest.json",
|
||||
);
|
||||
const roadmapPluginBundledPath = bundlePath.replace(
|
||||
"dist/bin.js",
|
||||
"dist/plugins/fusion-plugin-roadmap/bundled.js",
|
||||
);
|
||||
|
||||
describe("hasBuiltDashboardAssets", () => {
|
||||
beforeEach(() => {
|
||||
@@ -36,6 +40,7 @@ describe("hasBuiltDashboardAssets", () => {
|
||||
state.existingPaths.add(bundlePath);
|
||||
state.existingPaths.add(clientIndexPath);
|
||||
state.existingPaths.add(cursorPluginManifestPath);
|
||||
state.existingPaths.add(roadmapPluginBundledPath);
|
||||
|
||||
expect(hasBuiltDashboardAssets()).toBe(false);
|
||||
});
|
||||
@@ -44,6 +49,7 @@ describe("hasBuiltDashboardAssets", () => {
|
||||
state.existingPaths.add(bundlePath);
|
||||
state.existingPaths.add(clientIndexPath);
|
||||
state.existingPaths.add(cursorPluginManifestPath);
|
||||
state.existingPaths.add(roadmapPluginBundledPath);
|
||||
state.existingPaths.add(openclawMcpSchemaServerPath);
|
||||
|
||||
expect(hasBuiltDashboardAssets()).toBe(false);
|
||||
@@ -53,6 +59,7 @@ describe("hasBuiltDashboardAssets", () => {
|
||||
state.existingPaths.add(bundlePath);
|
||||
state.existingPaths.add(clientIndexPath);
|
||||
state.existingPaths.add(cursorPluginManifestPath);
|
||||
state.existingPaths.add(roadmapPluginBundledPath);
|
||||
state.existingPaths.add(openclawMcpSchemaServerPath);
|
||||
state.existingPaths.add(droidPluginMcpServerPath);
|
||||
|
||||
@@ -63,6 +70,7 @@ describe("hasBuiltDashboardAssets", () => {
|
||||
state.existingPaths.add(bundlePath);
|
||||
state.existingPaths.add(clientIndexPath);
|
||||
state.existingPaths.add(cursorPluginManifestPath);
|
||||
state.existingPaths.add(roadmapPluginBundledPath);
|
||||
state.existingPaths.add(openclawMcpSchemaServerPath);
|
||||
state.existingPaths.add(droidPluginMcpServerPath);
|
||||
state.indexHtml = dashboardClientStubMarker;
|
||||
|
||||
@@ -7,6 +7,7 @@ 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");
|
||||
const roadmapPluginBundledPath = join(cliRoot, "dist", "plugins", "fusion-plugin-roadmap", "bundled.js");
|
||||
export const openclawMcpSchemaServerPath = join(
|
||||
cliRoot,
|
||||
"dist",
|
||||
@@ -47,6 +48,7 @@ export function hasBuiltDashboardAssets(): boolean {
|
||||
!existsSync(bundlePath) ||
|
||||
!existsSync(clientIndexPath) ||
|
||||
!existsSync(cursorPluginManifestPath) ||
|
||||
!existsSync(roadmapPluginBundledPath) ||
|
||||
!existsSync(openclawMcpSchemaServerPath) ||
|
||||
!existsSync(droidPluginMcpServerPath)
|
||||
) {
|
||||
|
||||
@@ -37,9 +37,11 @@ describe("CLI bundle output", () => {
|
||||
expect(content).not.toMatch(/from\s+["']@fusion\/core["']/);
|
||||
expect(content).not.toMatch(/from\s+["']@fusion\/dashboard["']/);
|
||||
expect(content).not.toMatch(/from\s+["']@fusion\/engine["']/);
|
||||
expect(content).not.toMatch(/from\s+["']@fusion-plugin-examples\/roadmap["']/);
|
||||
expect(content).not.toContain('"@fusion/core"');
|
||||
expect(content).not.toContain('"@fusion/dashboard"');
|
||||
expect(content).not.toContain('"@fusion/engine"');
|
||||
expect(content).not.toContain('"@fusion-plugin-examples/roadmap"');
|
||||
});
|
||||
|
||||
it("does not contain runtime memory-backend side-load imports", () => {
|
||||
@@ -178,6 +180,26 @@ describe("CLI bundle output", () => {
|
||||
expect(stagedPkg.exports?.["."]?.import).toBe("./bundled.js");
|
||||
});
|
||||
|
||||
it("dist/plugins/fusion-plugin-roadmap/ is staged as bundled runtime output", () => {
|
||||
const stagedRoot = join(cliRoot, "dist", "plugins", "fusion-plugin-roadmap");
|
||||
const manifestPath = join(stagedRoot, "manifest.json");
|
||||
const packageJsonPath = join(stagedRoot, "package.json");
|
||||
|
||||
expect(existsSync(manifestPath)).toBe(true);
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8")) as { id?: string; name?: string };
|
||||
expect(manifest.id).toBe("roadmap-planner");
|
||||
expect(typeof manifest.name).toBe("string");
|
||||
expect(manifest.name?.length).toBeGreaterThan(0);
|
||||
|
||||
expect(existsSync(join(stagedRoot, "bundled.js"))).toBe(true);
|
||||
expect(existsSync(join(stagedRoot, "src"))).toBe(false);
|
||||
|
||||
const stagedPkg = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as {
|
||||
exports?: { "."?: { import?: string } };
|
||||
};
|
||||
expect(stagedPkg.exports?.["."]?.import).toBe("./bundled.js");
|
||||
});
|
||||
|
||||
it("dist/plugins/fusion-plugin-whatsapp-chat/ is staged with a valid manifest", () => {
|
||||
const stagedRoot = join(cliRoot, "dist", "plugins", "fusion-plugin-whatsapp-chat");
|
||||
const manifestPath = join(stagedRoot, "manifest.json");
|
||||
|
||||
@@ -22,6 +22,11 @@ function loadRootPackageJson(): any {
|
||||
return JSON.parse(readFileSync(path, "utf-8"));
|
||||
}
|
||||
|
||||
function loadCliPrepackScript(): string {
|
||||
const path = join(workspaceRoot, "packages", "cli", "scripts", "prepare-publish-manifest.mjs");
|
||||
return readFileSync(path, "utf-8");
|
||||
}
|
||||
|
||||
function hasProjectArg(script: string | undefined, project: string): boolean {
|
||||
const parts = script?.trim().split(/\s+/) ?? [];
|
||||
return parts.some((part, index) => part === "--project" && parts[index + 1] === project);
|
||||
@@ -29,6 +34,7 @@ function hasProjectArg(script: string | undefined, project: string): boolean {
|
||||
|
||||
describe("CLI package.json publishing config", () => {
|
||||
const pkg = loadPackageJson("cli");
|
||||
const prepackScript = loadCliPrepackScript();
|
||||
|
||||
it('has "bin" field with fn pointing to ./dist/bin.js', () => {
|
||||
expect(pkg.bin).toBeDefined();
|
||||
@@ -85,6 +91,12 @@ describe("CLI package.json publishing config", () => {
|
||||
expect(deps).toContain("ioredis");
|
||||
});
|
||||
|
||||
it("prepack manifest rewrite strips workspace-only plugin/tooling devDependencies", () => {
|
||||
expect(prepackScript).toContain('delete devDependencies["@fusion/pi-claude-cli"]');
|
||||
expect(prepackScript).toContain('delete devDependencies["@fusion/pi-llama-cpp"]');
|
||||
expect(prepackScript).toContain('delete devDependencies["@fusion-plugin-examples/roadmap"]');
|
||||
});
|
||||
|
||||
// Generalized guard derived from tsup.config.ts. Any non-builtin module
|
||||
// marked `external` MUST be a runtime dep (so `npm install @runfusion/fusion`
|
||||
// can resolve it after publish), and any module pulled in via `noExternal`
|
||||
|
||||
Reference in New Issue
Block a user