Archived
fix: prefer gateway plugins over remote duplicates
This commit is contained in:
@@ -325,6 +325,69 @@ describe("PluginRegistry", () => {
|
||||
expect(registry.getWorkspaceLabelItems({ ...initialAppState(), selectedMachine: testMachine("remote-1") }, workspace)).toEqual([{ type: "text", text: "remote" }]);
|
||||
expect(registry.getThemes()).toEqual([]);
|
||||
});
|
||||
|
||||
it("prefers gateway plugins over remote plugins with the same source id", () => {
|
||||
const registry = new PluginRegistry();
|
||||
const remotePluginId = machineScopedPluginId("remote-1", "shared-tools");
|
||||
const workspace = testWorkspace();
|
||||
registry.register({
|
||||
id: remotePluginId,
|
||||
machineId: "remote-1",
|
||||
sourcePluginId: "shared-tools",
|
||||
plugin: {
|
||||
apiVersion: 1,
|
||||
name: "Remote Shared Tools",
|
||||
activate: () => ({
|
||||
contributions: {
|
||||
actions: [{ id: "remote-action", title: "Remote Action", run: () => undefined }],
|
||||
workspacePanels: [{ id: "workspace.remote", title: "Remote", render: () => html`<p>Remote</p>` }],
|
||||
workspaceLabels: [{ id: "remote-label", items: () => [{ type: "text", text: "remote" }] }],
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.getActions(createContext({ selectedMachine: testMachine("remote-1") }).context).map((action) => action.id)).toContain(`${remotePluginId}:remote-action`);
|
||||
|
||||
registry.register({
|
||||
id: "shared-tools",
|
||||
plugin: {
|
||||
apiVersion: 1,
|
||||
name: "Gateway Shared Tools",
|
||||
activate: () => ({
|
||||
contributions: {
|
||||
actions: [{ id: "gateway-action", title: "Gateway Action", run: () => undefined }],
|
||||
workspacePanels: [{ id: "workspace.gateway", title: "Gateway", render: () => html`<p>Gateway</p>` }],
|
||||
workspaceLabels: [{ id: "gateway-label", items: () => [{ type: "text", text: "gateway" }] }],
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const remoteActions = registry.getActions(createContext({ selectedMachine: testMachine("remote-1") }).context).map((action) => action.id);
|
||||
expect(remoteActions).toContain("shared-tools:gateway-action");
|
||||
expect(remoteActions).not.toContain(`${remotePluginId}:remote-action`);
|
||||
|
||||
const panels = registry.getWorkspacePanels();
|
||||
expect(panels.find((panel) => panel.id === `${remotePluginId}:workspace.remote`)?.visible?.(createWorkspacePanelContext("remote-1"))).toBe(false);
|
||||
expect(panels.find((panel) => panel.id === "shared-tools:workspace.gateway")?.visible?.(createWorkspacePanelContext("remote-1"))).toBe(true);
|
||||
expect(registry.getWorkspaceLabelItems({ ...initialAppState(), selectedMachine: testMachine("remote-1") }, workspace)).toEqual([{ type: "text", text: "gateway" }]);
|
||||
});
|
||||
|
||||
it("does not activate remote duplicates when the gateway plugin is already registered", () => {
|
||||
const registry = new PluginRegistry();
|
||||
const remoteActivate = vi.fn(() => ({ contributions: { actions: [{ id: "remote-action", title: "Remote Action", run: () => undefined }] } }));
|
||||
registry.register({ id: "shared-tools", plugin: { apiVersion: 1, name: "Gateway Shared Tools", activate: () => ({ contributions: {} }) } });
|
||||
|
||||
registry.register({
|
||||
id: machineScopedPluginId("remote-1", "shared-tools"),
|
||||
machineId: "remote-1",
|
||||
sourcePluginId: "shared-tools",
|
||||
plugin: { apiVersion: 1, name: "Remote Shared Tools", activate: remoteActivate },
|
||||
});
|
||||
|
||||
expect(remoteActivate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function testWorkspace(patch: Partial<Workspace> = {}): Workspace {
|
||||
|
||||
@@ -13,6 +13,7 @@ type RegisteredPluginAction = Omit<PluginAction, "id"> & {
|
||||
pluginId: string;
|
||||
localId: string;
|
||||
machineId?: string;
|
||||
sourcePluginId?: string;
|
||||
};
|
||||
|
||||
export class PluginRegistry {
|
||||
@@ -22,29 +23,32 @@ export class PluginRegistry {
|
||||
private readonly themes: QualifiedThemeContribution[] = [];
|
||||
private readonly themePairs: QualifiedThemePairContribution[] = [];
|
||||
private readonly pluginIds = new Set<string>();
|
||||
private readonly gatewayPluginIds = new Set<string>();
|
||||
private readonly contributionIds = new Set<QualifiedContributionId>();
|
||||
|
||||
register(registration: PiWebPluginRegistration): void {
|
||||
const { id, plugin } = registration;
|
||||
this.validatePluginId(id);
|
||||
if (this.pluginIds.has(id)) throw new Error(`Duplicate plugin id: ${id}`);
|
||||
if (isDuplicateOfGatewayPlugin(registration, this.gatewayPluginIds)) return;
|
||||
this.pluginIds.add(id);
|
||||
|
||||
const apiVersion: unknown = plugin.apiVersion;
|
||||
if (apiVersion !== 1) throw new Error(`Unsupported plugin API version for ${id}: ${String(apiVersion)}`);
|
||||
const result = plugin.activate({ apiVersion: 1, pluginId: id, html, svg });
|
||||
const contributions = result.contributions;
|
||||
for (const action of contributions.actions ?? []) this.actions.push(this.qualifyAction(id, action, registration.machineId));
|
||||
for (const panel of contributions.workspacePanels ?? []) this.workspacePanels.push(this.qualifyWorkspacePanel(id, panel, registration.machineId));
|
||||
for (const contribution of contributions.workspaceLabels ?? []) this.workspaceLabels.push(this.qualifyWorkspaceLabelContribution(id, contribution, registration.machineId));
|
||||
for (const action of contributions.actions ?? []) this.actions.push(this.qualifyAction(id, action, registration.machineId, registration.sourcePluginId));
|
||||
for (const panel of contributions.workspacePanels ?? []) this.workspacePanels.push(this.qualifyWorkspacePanel(id, panel, registration.machineId, registration.sourcePluginId));
|
||||
for (const contribution of contributions.workspaceLabels ?? []) this.workspaceLabels.push(this.qualifyWorkspaceLabelContribution(id, contribution, registration.machineId, registration.sourcePluginId));
|
||||
if (registration.machineId === undefined) {
|
||||
for (const theme of contributions.themes ?? []) this.themes.push(this.qualifyTheme(id, theme));
|
||||
for (const pair of contributions.themePairs ?? []) this.themePairs.push(this.qualifyThemePair(id, pair));
|
||||
this.gatewayPluginIds.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
getActions(context: PluginRuntimeContext): QualifiedPluginAction[] {
|
||||
return this.actions.filter((action) => isActiveForMachine(action.machineId, runtimeContextMachineId(context))).map((action) => {
|
||||
return this.actions.filter((action) => isActiveForMachine(action.machineId, runtimeContextMachineId(context), action.sourcePluginId, this.gatewayPluginIds)).map((action) => {
|
||||
const scopedContext = pluginRuntimeContextFor(context, action.pluginId);
|
||||
const enabled = action.enabled?.(scopedContext);
|
||||
const qualified: QualifiedPluginAction = {
|
||||
@@ -85,12 +89,12 @@ export class PluginRegistry {
|
||||
});
|
||||
}
|
||||
|
||||
private qualifyAction(pluginId: string, action: PluginAction, machineId: string | undefined): RegisteredPluginAction {
|
||||
private qualifyAction(pluginId: string, action: PluginAction, machineId: string | undefined, sourcePluginId: string | undefined): RegisteredPluginAction {
|
||||
const id = this.qualify(pluginId, action.id);
|
||||
return { ...action, id, pluginId, localId: action.id, ...(machineId === undefined ? {} : { machineId }) };
|
||||
return { ...action, id, pluginId, localId: action.id, ...(machineId === undefined ? {} : { machineId }), ...(sourcePluginId === undefined ? {} : { sourcePluginId }) };
|
||||
}
|
||||
|
||||
private qualifyWorkspacePanel(pluginId: string, panel: WorkspacePanelContribution, machineId: string | undefined): QualifiedWorkspacePanelContribution {
|
||||
private qualifyWorkspacePanel(pluginId: string, panel: WorkspacePanelContribution, machineId: string | undefined, sourcePluginId: string | undefined): QualifiedWorkspacePanelContribution {
|
||||
const id = this.qualify(pluginId, panel.id);
|
||||
const badge = panel.badge;
|
||||
const visible = panel.visible;
|
||||
@@ -100,13 +104,13 @@ export class PluginRegistry {
|
||||
pluginId,
|
||||
localId: panel.id,
|
||||
...(machineId === undefined ? {} : { machineId }),
|
||||
visible: (context: WorkspacePanelContext) => isActiveForMachine(machineId, context.machine.id) && (visible?.(workspacePanelContextFor(context, pluginId)) ?? true),
|
||||
...(badge === undefined ? {} : { badge: (context: WorkspacePanelContext) => isActiveForMachine(machineId, context.machine.id) ? badge(workspacePanelContextFor(context, pluginId)) : undefined }),
|
||||
visible: (context: WorkspacePanelContext) => isActiveForMachine(machineId, context.machine.id, sourcePluginId, this.gatewayPluginIds) && (visible?.(workspacePanelContextFor(context, pluginId)) ?? true),
|
||||
...(badge === undefined ? {} : { badge: (context: WorkspacePanelContext) => isActiveForMachine(machineId, context.machine.id, sourcePluginId, this.gatewayPluginIds) ? badge(workspacePanelContextFor(context, pluginId)) : undefined }),
|
||||
render: (context: WorkspacePanelContext) => panel.render(workspacePanelContextFor(context, pluginId)),
|
||||
};
|
||||
}
|
||||
|
||||
private qualifyWorkspaceLabelContribution(pluginId: string, contribution: WorkspaceLabelContribution, machineId: string | undefined): QualifiedWorkspaceLabelContribution {
|
||||
private qualifyWorkspaceLabelContribution(pluginId: string, contribution: WorkspaceLabelContribution, machineId: string | undefined, sourcePluginId: string | undefined): QualifiedWorkspaceLabelContribution {
|
||||
const id = this.qualify(pluginId, contribution.id);
|
||||
const visible = contribution.visible;
|
||||
const items = contribution.items;
|
||||
@@ -116,8 +120,8 @@ export class PluginRegistry {
|
||||
pluginId,
|
||||
localId: contribution.id,
|
||||
...(machineId === undefined ? {} : { machineId }),
|
||||
visible: (context) => isActiveForMachine(machineId, context.machine.id) && (visible?.(context) ?? true),
|
||||
items: (context) => isActiveForMachine(machineId, context.machine.id) ? items(context) : [],
|
||||
visible: (context) => isActiveForMachine(machineId, context.machine.id, sourcePluginId, this.gatewayPluginIds) && (visible?.(context) ?? true),
|
||||
items: (context) => isActiveForMachine(machineId, context.machine.id, sourcePluginId, this.gatewayPluginIds) ? items(context) : [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,8 +182,16 @@ export function installWorkspacePanelScope(context: WorkspacePanelContext, scope
|
||||
return context;
|
||||
}
|
||||
|
||||
function isActiveForMachine(machineId: string | undefined, selectedMachineId: string): boolean {
|
||||
return machineId === undefined || machineId === selectedMachineId;
|
||||
function isDuplicateOfGatewayPlugin(registration: PiWebPluginRegistration, gatewayPluginIds: ReadonlySet<string>): boolean {
|
||||
return registration.machineId !== undefined && registration.sourcePluginId !== undefined && gatewayPluginIds.has(registration.sourcePluginId);
|
||||
}
|
||||
|
||||
function isActiveForMachine(machineId: string | undefined, selectedMachineId: string, sourcePluginId: string | undefined, gatewayPluginIds: ReadonlySet<string>): boolean {
|
||||
return machineId === undefined || (machineId === selectedMachineId && !isHiddenByGatewayPlugin(sourcePluginId, gatewayPluginIds));
|
||||
}
|
||||
|
||||
function isHiddenByGatewayPlugin(sourcePluginId: string | undefined, gatewayPluginIds: ReadonlySet<string>): boolean {
|
||||
return sourcePluginId !== undefined && gatewayPluginIds.has(sourcePluginId);
|
||||
}
|
||||
|
||||
function runtimeContextMachineId(context: PluginRuntimeContext): string {
|
||||
|
||||
Reference in New Issue
Block a user