feat(FN-4128): fix bundled plugin entry resolution and surface load errors

Bundled plugin entry resolution is fixed to use proper import.meta.url path normalization, with plugin load errors now surfaced in the PluginManager UI and comprehensive regression tests added for the bundled plugin path migration and state handling.

Fusion-Task-Id: FN-4128
This commit is contained in:
Fusion
2026-05-12 10:30:50 -07:00
committed by gsxdsm
parent 997fc8e4f9
commit 742c5edd11
9 changed files with 336 additions and 19 deletions

View File

@@ -425,6 +425,31 @@ describe("PluginLoader", () => {
expect(updated.state).toBe("started");
});
it("recovers a previously errored plugin to started and clears the stored error", async () => {
await pluginStore.init();
const plugin = makePlugin(makeManifest({ id: "recover-error-test" }));
const pluginDir = join(rootDir, "plugins");
const pluginPath = await writePluginModule(pluginDir, "recover-error.js", plugin);
await pluginStore.registerPlugin({
manifest: plugin.manifest,
path: pluginPath,
});
await pluginStore.updatePluginState("recover-error-test", "error", "previous load failed");
const loader = new PluginLoader({
pluginStore,
taskStore: mockTaskStore,
});
await loader.loadPlugin("recover-error-test");
const updated = await pluginStore.getPlugin("recover-error-test");
expect(updated.state).toBe("started");
expect(updated.error ?? null).toBeNull();
});
it("skips disabled plugins", async () => {
await pluginStore.init();

View File

@@ -10,6 +10,7 @@
*/
import { basename, dirname, extname, isAbsolute, resolve } from "node:path";
import { stat } from "node:fs/promises";
import { copyFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
import { EventEmitter } from "node:events";
@@ -313,6 +314,18 @@ export class PluginLoader extends EventEmitter<{
return this.loadedModules.get(path)!;
}
let pathStats;
try {
pathStats = await stat(path);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
throw new Error(`Plugin entry does not exist: ${path} (${errorMessage})`);
}
if (pathStats.isDirectory()) {
throw new Error(`Plugin entry must be a file, got directory: ${path}`);
}
// Dynamic import - normalize to file URL so query params are honored
// consistently across Node + Vitest environments.
const moduleUrl = pathToFileURL(path).href;