FN-6106: block plugin view re-exports from server entries

Add an ESLint guard to keep plugin dashboard views out of server entrypoints.

- add a custom fusion/no-plugin-view-reexport ESLint rule for plugin src/index.ts files
- flag relative re-exports of *-view entrypoints so CSS-bearing dashboard modules stay out of Node-loaded plugin entries
- document the server-entry export constraint in the plugin authoring guide

Files changed:
 docs/PLUGIN_AUTHORING.md |  1 +
 eslint.config.mjs        | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 48 insertions(+)

Fusion-Task-Id: FN-6106

Fusion-Task-Lineage: 6bbfa073-e7fd-42a9-a332-c638b81cd55f
This commit is contained in:
gsxdsm
2026-06-09 10:35:58 -07:00
parent 0b0186a22e
commit 47e82408af
2 changed files with 48 additions and 0 deletions

View File

@@ -763,6 +763,7 @@ The host then renders plugin views via `PluginDashboardViewHost` using the compo
Bundled workspace plugin pattern:
- Keep plugin package under `plugins/` (for example `plugins/fusion-plugin-roadmap`)
- Export backend/plugin entry from `src/index.ts` and keep dashboard view exports in the plugin package (for example `./dashboard-view`)
- Do not re-export dashboard view entrypoints (including `dashboard-view`, `manage-view`, or other `-view` modules) from `src/index.ts`; the `fusion/no-plugin-view-reexport` ESLint guard catches violations because server-side plugin entries must not transitively load CSS
- Register the lazy dashboard component in host code (currently `packages/dashboard/app/plugins/registerBundledPluginViews.ts`)
- CLI bundling inlines backend plugin code from workspace packages; dashboard view modules are imported by the dashboard build via the host registry

View File

@@ -69,6 +69,36 @@ const detachedSpawnGuard = {
},
};
const noPluginViewReexport = {
meta: {
type: "problem",
docs: {
description: "ban dashboard view re-exports from plugin server entry points",
},
schema: [],
},
create(context) {
function isViewEntrypointReexportSource(value) {
return typeof value === "string"
&& value.startsWith(".")
&& /(?:^|\/)[^/]*-view(?:\.(?:js|tsx))?$/.test(value);
}
return {
ExportNamedDeclaration(node) {
if (!isViewEntrypointReexportSource(node.source?.value)) {
return;
}
context.report({
node: node.source,
message:
"Plugin server entry (src/index.ts) must not re-export dashboard view components. Use a dedicated subpath export (e.g. './dashboard-view') instead — see docs/PLUGIN_AUTHORING.md.",
});
},
};
},
};
/**
* ESLint Flat Config for Fusion Workspace
*
@@ -339,6 +369,23 @@ export default tseslint.config(
},
},
// ─────────────────────────────────────────────────────────────
// PLUGIN SERVER ENTRYPOINTS — keep dashboard views out of Node-loaded entries
// ─────────────────────────────────────────────────────────────
{
files: ["plugins/**/src/index.ts", "plugins/examples/**/src/index.ts"],
plugins: {
fusion: {
rules: {
"no-plugin-view-reexport": noPluginViewReexport,
},
},
},
rules: {
"fusion/no-plugin-view-reexport": "error",
},
},
// ─────────────────────────────────────────────────────────────
// AGENT SKILL TEMPLATES — template code with underscore prefix support
// (agent prompt templates use _prefixed placeholders intentionally)