diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx
index 89ac938505..b224ea491e 100644
--- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx
+++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx
@@ -64,68 +64,3 @@ export const PageBlueprint = createExtensionBlueprint({
}
},
});
-
-const homePage = PageBlueprint.make({
- params: {
- defaultPath: '/home',
- loader: async () =>
Home
,
- },
-});
-
-const homePlugin = createPlugin({
- id: 'asd',
- extensions: [homePage],
-});
-
-// overrides-package locally
-
-/*
-
-# TODO
-- [ ] Decorate getExtension(...) extension definitions with a namespace
-- [ ] Implement plugin.override([...])
-
-*/
-
-// import plugin from '@backstage/plugin-home';
-
-// export const exp1 = plugin.override([
-// plugin.getExtension('page:home').override({
-// *factory(originalFactory) {
-// yield* originalFactory();
-// yield coreExtensionData.routePath('/backstage');
-// // return [...originalFactory(), coreExtensionData.routePath('/backstage')];
-// // return originalFactory({
-// // defaultPath: '/backstage',
-// // });
-// },
-// }),
-// PageBlueprint.make({
-// name: 'home',
-// params: {
-// defaultPath: '/home',
-// loader: async () => Home
,
-// },
-// })
-// ]);
-
-// // kind:namespace/name
-// // entity-card:github/issues
-// // entity-card:github/pull-requests
-// // page:catalog
-
-// export const exp2 = createExtensionOverrides({
-// extensions: [
-// homePlugin.getExtension('page:home').override({
-// // namespace: 'home',
-// *factory(originalFactory) {
-// yield* originalFactory();
-// yield coreExtensionData.routePath('/backstage');
-// // return [...originalFactory(), coreExtensionData.routePath('/backstage')];
-// // return originalFactory({
-// // defaultPath: '/backstage',
-// // });
-// },
-// })
-// ]
-// })
diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts
index 76cc030be3..f1a0817c80 100644
--- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts
+++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts
@@ -148,7 +148,28 @@ describe('createPlugin', () => {
});
expect(plugin).toBeDefined();
- expect(plugin.getExtension('test/1')).toBe(Extension1);
+ expect(plugin.getExtension('test/1')).toMatchInlineSnapshot(`
+ {
+ "$$type": "@backstage/ExtensionDefinition",
+ "attachTo": {
+ "id": "test/output",
+ "input": "names",
+ },
+ "configSchema": undefined,
+ "disabled": false,
+ "factory": [Function],
+ "inputs": {},
+ "kind": undefined,
+ "name": "1",
+ "namespace": "test",
+ "output": [
+ [Function],
+ ],
+ "override": [Function],
+ "toString": [Function],
+ "version": "v2",
+ }
+ `);
// @ts-expect-error
expect(plugin.getExtension('nonexistent')).toBeUndefined();
@@ -250,4 +271,42 @@ describe('createPlugin', () => {
}),
).toThrow("Plugin 'test' provided duplicate extensions: test/2, test/3");
});
+
+ describe('overrides', () => {
+ it('should allow overriding extensions that have a matching ID, while keeping old extensions that do not have overlapping IDs', async () => {
+ const plugin = createPlugin({
+ id: 'test',
+ extensions: [Extension1, Extension2, outputExtension],
+ });
+
+ expect(plugin.getExtension('test/1')).toMatchObject({
+ namespace: 'test',
+ });
+
+ await renderWithEffects(
+ createTestAppRoot({
+ features: [
+ plugin.withOverrides({
+ extensions: [
+ plugin.getExtension('test/1').override({
+ factory() {
+ return [nameExtensionDataRef('overridden')];
+ },
+ }),
+ ],
+ }),
+ ],
+ config: {
+ app: {
+ extensions: [{ 'app/root': false }],
+ },
+ },
+ }),
+ );
+
+ await expect(
+ screen.findByText('Names: extension-2, overridden'),
+ ).resolves.toBeInTheDocument();
+ });
+ });
});
diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.ts
index 1d2a12ffe2..2f7a813c90 100644
--- a/packages/frontend-plugin-api/src/wiring/createPlugin.ts
+++ b/packages/frontend-plugin-api/src/wiring/createPlugin.ts
@@ -78,7 +78,7 @@ export function createPlugin<
for (const def of options.extensions ?? []) {
const ext = resolveExtensionDefinition(def, { namespace: options.id });
extensions.push(ext);
- extensionDefinitionsById.set(ext.id, def);
+ extensionDefinitionsById.set(ext.id, { ...def, namespace: options.id });
}
if (extensions.length !== extensionDefinitionsById.size) {
@@ -110,6 +110,23 @@ export function createPlugin<
toString() {
return `Plugin{id=${options.id}}`;
},
+ withOverrides(overrides) {
+ const overrideExtensionIds = new Set(
+ overrides.extensions.map(
+ e => resolveExtensionDefinition(e, { namespace: options.id }).id,
+ ),
+ );
+ const nonOverridenExtensions = (options.extensions ?? []).filter(
+ e =>
+ !overrideExtensionIds.has(
+ resolveExtensionDefinition(e, { namespace: options.id }).id,
+ ),
+ );
+ return createPlugin({
+ ...options,
+ extensions: [...nonOverridenExtensions, ...overrides.extensions],
+ });
+ },
} as InternalBackstagePlugin;
}
diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts
index b1afd4e7ea..4a7d972b37 100644
--- a/packages/frontend-plugin-api/src/wiring/types.ts
+++ b/packages/frontend-plugin-api/src/wiring/types.ts
@@ -51,6 +51,9 @@ export interface BackstagePlugin<
readonly routes: TRoutes;
readonly externalRoutes: TExternalRoutes;
getExtension(id: TId): TExtensionMap[TId];
+ withOverrides(options: {
+ extensions: Array>;
+ }): BackstagePlugin;
}
/** @public */