From 1c5d1d1da0af28b979b1fcacf9044e8ce6be2b2b Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 8 Aug 2025 15:06:37 +0200 Subject: [PATCH 1/6] chore: force installation of extensions in app plugin Signed-off-by: benjdlambert --- plugins/app/src/extensions/SwappableComponentsApi.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/app/src/extensions/SwappableComponentsApi.ts b/plugins/app/src/extensions/SwappableComponentsApi.ts index ea57a6f487..a1a4c826ca 100644 --- a/plugins/app/src/extensions/SwappableComponentsApi.ts +++ b/plugins/app/src/extensions/SwappableComponentsApi.ts @@ -39,9 +39,14 @@ export const SwappableComponentsApi = ApiBlueprint.makeWithOverrides({ deps: {}, factory: () => DefaultSwappableComponentsApi.fromComponents( - inputs.components.map(i => - i.get(SwappableComponentBlueprint.dataRefs.component), - ), + inputs.components.map(i => { + if (i.node.spec.plugin?.id !== 'app') { + throw new Error( + `SwappableComponents can only be installed as an extension in the app plugin. You can either use appPlugin.override(), or provide a module for the app-plugin with the extension there instead. id={${i.node.spec.id}}`, + ); + } + return i.get(SwappableComponentBlueprint.dataRefs.component); + }), ), }), ); From 91cbdf4ee4c5bfe40606fad8f6bd0e0c2d5a6efb Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 8 Aug 2025 15:10:34 +0200 Subject: [PATCH 2/6] chore: changeset Signed-off-by: benjdlambert --- .changeset/cold-lemons-design.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cold-lemons-design.md diff --git a/.changeset/cold-lemons-design.md b/.changeset/cold-lemons-design.md new file mode 100644 index 0000000000..b72546ea20 --- /dev/null +++ b/.changeset/cold-lemons-design.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': patch +--- + +Force installation of `SwappableComponent` extensions using the `app` plugin From bd0279898191f9f58a333c52558a409fd15384d6 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 8 Aug 2025 15:21:59 +0200 Subject: [PATCH 3/6] chore: make a warning Signed-off-by: benjdlambert --- plugins/app/src/extensions/SwappableComponentsApi.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/app/src/extensions/SwappableComponentsApi.ts b/plugins/app/src/extensions/SwappableComponentsApi.ts index a1a4c826ca..c969cbb057 100644 --- a/plugins/app/src/extensions/SwappableComponentsApi.ts +++ b/plugins/app/src/extensions/SwappableComponentsApi.ts @@ -41,8 +41,9 @@ export const SwappableComponentsApi = ApiBlueprint.makeWithOverrides({ DefaultSwappableComponentsApi.fromComponents( inputs.components.map(i => { if (i.node.spec.plugin?.id !== 'app') { - throw new Error( - `SwappableComponents can only be installed as an extension in the app plugin. You can either use appPlugin.override(), or provide a module for the app-plugin with the extension there instead. id={${i.node.spec.id}}`, + // eslint-disable-next-line no-console + console.warn( + `SwappableComponents should only be installed as an extension in the app plugin. You can either use appPlugin.override(), or provide a module for the app-plugin with the extension there instead. id={${i.node.spec.id}}`, ); } return i.get(SwappableComponentBlueprint.dataRefs.component); From 53ab8c9e4e2e2a8b5d0f644f3456bc79b4bb3592 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 8 Aug 2025 15:23:32 +0200 Subject: [PATCH 4/6] chore: updating changeset Signed-off-by: benjdlambert --- .changeset/cold-lemons-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cold-lemons-design.md b/.changeset/cold-lemons-design.md index b72546ea20..9bdadac9c7 100644 --- a/.changeset/cold-lemons-design.md +++ b/.changeset/cold-lemons-design.md @@ -2,4 +2,4 @@ '@backstage/plugin-app': patch --- -Force installation of `SwappableComponent` extensions using the `app` plugin +Log a warning when `SwappableComponent` extensions are installed outside of using the `app` plugin From 4be2c4d61db7b3366b349e99db5955ef1554e033 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Sat, 9 Aug 2025 11:24:18 +0200 Subject: [PATCH 5/6] chore: filter out non-app extensions and log Signed-off-by: benjdlambert --- .../src/extensions/SwappableComponentsApi.ts | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/plugins/app/src/extensions/SwappableComponentsApi.ts b/plugins/app/src/extensions/SwappableComponentsApi.ts index c969cbb057..c848b0a575 100644 --- a/plugins/app/src/extensions/SwappableComponentsApi.ts +++ b/plugins/app/src/extensions/SwappableComponentsApi.ts @@ -37,18 +37,32 @@ export const SwappableComponentsApi = ApiBlueprint.makeWithOverrides({ defineParams({ api: swappableComponentsApiRef, deps: {}, - factory: () => - DefaultSwappableComponentsApi.fromComponents( - inputs.components.map(i => { - if (i.node.spec.plugin?.id !== 'app') { - // eslint-disable-next-line no-console - console.warn( - `SwappableComponents should only be installed as an extension in the app plugin. You can either use appPlugin.override(), or provide a module for the app-plugin with the extension there instead. id={${i.node.spec.id}}`, - ); - } - return i.get(SwappableComponentBlueprint.dataRefs.component); - }), - ), + factory: () => { + const nonAppExtensions = inputs.components.filter( + i => i.node.spec.plugin?.id !== 'app', + ); + + if (nonAppExtensions.length > 0) { + // eslint-disable-next-line no-console + console.warn( + `SwappableComponents should only be installed as an extension in the app plugin. + You can either use appPlugin.override(), or provide a module for the app-plugin with the extension there instead. + Invalid extensions: ${nonAppExtensions + .map(i => i.node.spec.id) + .join(', ')}`, + ); + } + + const appExtensions = inputs.components.filter( + i => i.node.spec.plugin?.id === 'app', + ); + + return DefaultSwappableComponentsApi.fromComponents( + appExtensions.map(i => + i.get(SwappableComponentBlueprint.dataRefs.component), + ), + ); + }, }), ); }, From 27043591486b4ce8916f8c433036153dfd4b4650 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 11 Aug 2025 09:38:33 +0200 Subject: [PATCH 6/6] chore: refactor tests to fix Signed-off-by: benjdlambert --- .../DefaultSwappableComponentsApi.test.tsx | 25 ++++++++++++++++--- .../src/extensions/SwappableComponentsApi.ts | 4 +-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx index 823d48e21c..07ef891926 100644 --- a/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx +++ b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx @@ -17,6 +17,7 @@ import { ApiBlueprint, createExtensionInput, + createFrontendModule, createSwappableComponent, SwappableComponentBlueprint, swappableComponentsApiRef, @@ -202,7 +203,13 @@ describe('DefaultSwappableComponentsApi', () => { }); renderInTestApp(, { - extensions: [api, override], + extensions: [api], + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [override], + }), + ], }); await expect( @@ -225,7 +232,13 @@ describe('DefaultSwappableComponentsApi', () => { }); renderInTestApp(, { - extensions: [api, override], + extensions: [api], + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [override], + }), + ], }); await expect( @@ -252,7 +265,13 @@ describe('DefaultSwappableComponentsApi', () => { }); renderInTestApp(, { - extensions: [api, override], + extensions: [api], + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [override], + }), + ], }); await expect( diff --git a/plugins/app/src/extensions/SwappableComponentsApi.ts b/plugins/app/src/extensions/SwappableComponentsApi.ts index c848b0a575..f261d999c0 100644 --- a/plugins/app/src/extensions/SwappableComponentsApi.ts +++ b/plugins/app/src/extensions/SwappableComponentsApi.ts @@ -45,9 +45,7 @@ export const SwappableComponentsApi = ApiBlueprint.makeWithOverrides({ if (nonAppExtensions.length > 0) { // eslint-disable-next-line no-console console.warn( - `SwappableComponents should only be installed as an extension in the app plugin. - You can either use appPlugin.override(), or provide a module for the app-plugin with the extension there instead. - Invalid extensions: ${nonAppExtensions + `SwappableComponents should only be installed as an extension in the app plugin. You can either use appPlugin.override(), or provide a module for the app-plugin with the extension there instead. Invalid extensions: ${nonAppExtensions .map(i => i.node.spec.id) .join(', ')}`, );