feat(plugins,host): pluginRoot officiel transmis au contexte activate(ctx) des plugins — #285 (domain ports + infrastructure plugin: racine du package installé résolue depuis la même source de vérité que la substitution manifest ${pluginRoot}; application runtime_catalog + slash_command; backend dto + app-tauri dto_plugins; frontend domain + plugins runtime loader transmettent pluginRoot au contexte activé; bump pointeur IdeaSDK e3a0f46 via feature/285-plugin-root-runtime-api; QA verte: cargo check domain/application/infrastructure/backend/app-tauri, runtime_catalog 5/5, plugin_install_load 5/5, dto_plugins 2/2, frontend typecheck + 33 tests/3 fichiers, cargo fmt --check, git diff --check; réserves: rebuild AppImage + validation live ctx.pluginRoot dans le binaire actif restants)

This commit is contained in:
2026-09-08 18:35:34 +02:00
parent 46a1d1a31b
commit e499a0fd34
13 changed files with 88 additions and 4 deletions

View File

@ -29,6 +29,7 @@ function entry(overrides: Partial<PluginRuntimePlugin> & { bundleUrl: string }):
id: "mock.plugin",
displayName: "Mock Plugin",
version: "1.0.0",
pluginRoot: "/data/idea/plugins/installed/mock.plugin",
contentHash: "abc123",
contributes: emptyContributes(),
...overrides,
@ -39,17 +40,25 @@ describe("loadPlugins", () => {
it("loads a well-formed plugin bundle and calls activate(ctx)", async () => {
const bundle = dataUrl(`
export function activate(ctx) {
globalThis.__activatedWith = ctx.pluginId;
globalThis.__activatedWith = { pluginId: ctx.pluginId, pluginRoot: ctx.pluginRoot };
return {};
}
`);
const { registry, failures } = await loadPlugins(
[entry({ id: "dev.acme.one", displayName: "One", bundleUrl: bundle })],
[entry({
id: "dev.acme.one",
displayName: "One",
bundleUrl: bundle,
pluginRoot: "/data/idea/plugins/installed/dev.acme.one",
})],
gateways,
);
expect(failures).toEqual([]);
expect(registry.list().map((p) => p.pluginId)).toEqual(["dev.acme.one"]);
expect((globalThis as Record<string, unknown>).__activatedWith).toBe("dev.acme.one");
expect((globalThis as Record<string, unknown>).__activatedWith).toEqual({
pluginId: "dev.acme.one",
pluginRoot: "/data/idea/plugins/installed/dev.acme.one",
});
});
it("loads an SDK-style default export containing activate(ctx)", async () => {
@ -242,6 +251,7 @@ describe("loadPlugins", () => {
"menu",
"pluginDisplayName",
"pluginId",
"pluginRoot",
"services",
"storage",
"subscriptions",

View File

@ -38,6 +38,7 @@ export type { PluginGatewaySet } from "./registry";
export interface IdeaPluginContext {
pluginId: string;
pluginRoot: string;
pluginDisplayName: string;
version: string;
logger: PluginLogger;
@ -270,6 +271,7 @@ async function loadOne(
const pluginId = safePluginId(entry);
const displayName = nonEmptyString(entryObject.displayName) ?? pluginId;
const version = nonEmptyString(entryObject.version) ?? "";
const pluginRoot = nonEmptyString(entryObject.pluginRoot);
let activation: void | PluginActivation = undefined;
const subscriptions: Disposable[] = [];
try {
@ -280,6 +282,9 @@ async function loadOne(
if (!bundleUrl) {
throw new Error("missing plugin bundle URL");
}
if (!pluginRoot) {
throw new Error("missing installed plugin root");
}
installHostReactImportMap();
const mod = resolveIdeaPluginModule(
@ -308,6 +313,7 @@ async function loadOne(
const ctx: IdeaPluginContext = {
pluginId,
pluginRoot,
pluginDisplayName: displayName,
version,
logger: createPluginLogger(entry),