feat(FN-4050): add one-click install support for reports plugin

- Add reports plugin to bundled plugin resolver so it can be installed from packaged assets
- Expose reports plugin in dashboard plugin manager and server routes for one-click install flows
- Add CLI and dashboard test coverage for bundled install behavior and plugin route availability
- Update reports/plugin management/settings docs and include a changeset for @runfusion/fusion
This commit is contained in:
Fusion
2026-05-11 21:48:19 -07:00
committed by gsxdsm
parent 287ebaf102
commit 4a09924c56
11 changed files with 148 additions and 2 deletions

View File

@@ -36,6 +36,7 @@ const BUNDLED_PLUGIN_ID = "fusion-plugin-dependency-graph";
const HERMES_PLUGIN_ID = "fusion-plugin-hermes-runtime";
const CURSOR_PLUGIN_ID = "fusion-plugin-cursor-runtime";
const ROADMAP_PLUGIN_ID = "fusion-plugin-roadmap";
const REPORTS_PLUGIN_ID = "fusion-plugin-reports";
const CLI_PRINTING_PRESS_PLUGIN_ID = "fusion-plugin-cli-printing-press";
function makeManifest(overrides?: Partial<{ id: string; version: string; name: string }>) {
@@ -225,6 +226,10 @@ describe("ensureBundledDependencyGraphPluginInstalled", () => {
it("includes CLI printing press plugin in bundled plugin ids", () => {
expect(BUNDLED_PLUGIN_IDS).toContain(CLI_PRINTING_PRESS_PLUGIN_ID);
});
it("includes reports plugin in bundled plugin ids", () => {
expect(BUNDLED_PLUGIN_IDS).toContain(REPORTS_PLUGIN_ID);
});
it("fresh install: registers and loads the plugin when not in DB", async () => {
setupBundleExists();
const store = makePluginStore();
@@ -417,6 +422,33 @@ describe("ensureBundledDependencyGraphPluginInstalled", () => {
);
});
it("registers reports plugin via generic bundled installer", async () => {
const manifest = makeManifest({ id: REPORTS_PLUGIN_ID, name: "Reports" });
mockExistsSync.mockImplementation((p: string) => {
if (p.endsWith("manifest.json") && p.includes(REPORTS_PLUGIN_ID)) return true;
if (p.endsWith("/src/index.ts") && p.includes(REPORTS_PLUGIN_ID)) return true;
return false;
});
mockReadFile.mockResolvedValue(JSON.stringify(manifest));
mockValidatePluginManifest.mockReturnValue({ valid: true, errors: [] });
const store = makePluginStore();
const loader = makePluginLoader();
const result = await ensureBundledPluginInstalled(
store as unknown as import("@fusion/core").PluginStore,
loader as unknown as import("@fusion/core").PluginLoader,
REPORTS_PLUGIN_ID,
);
expect(result).toBe("installed");
expect(store.registerPlugin).toHaveBeenCalledWith(
expect.objectContaining({ manifest: expect.objectContaining({ id: REPORTS_PLUGIN_ID }) }),
);
const registerCall = store.registerPlugin.mock.calls[0]?.[0] as { path: string };
expect(registerCall.path).toContain(REPORTS_PLUGIN_ID);
});
it("registers Hermes from bundled.js when bundled, src, and dist entries all exist", async () => {
const manifest = makeManifest({ id: HERMES_PLUGIN_ID, name: "Hermes Runtime" });
mockExistsSync.mockImplementation((p: string) => {

View File

@@ -9,6 +9,7 @@ const CURSOR_RUNTIME_PLUGIN_ID = "fusion-plugin-cursor-runtime";
export const BUNDLED_PLUGIN_IDS = [
"fusion-plugin-dependency-graph",
"fusion-plugin-reports",
"fusion-plugin-whatsapp-chat",
"fusion-plugin-roadmap",
"fusion-plugin-hermes-runtime",