refactor(frontend-plugin,app-api): make app spec plugin required

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2025-08-07 12:29:58 +02:00
parent 8fefcd2fed
commit 8e21c4d993
7 changed files with 100 additions and 12 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': minor
---
Use an empty root plugin for built-in extension app node specs.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': minor
---
**BREAKING:** The `AppNodeSpec.plugin` property is now required.
@@ -24,6 +24,7 @@ import {
createExtension,
createExtensionDataRef,
createExtensionInput,
createFrontendPlugin,
} from '@backstage/frontend-plugin-api';
import {
createAppNodeInstance,
@@ -56,7 +57,7 @@ function makeSpec<TConfig, TConfigInput>(
attachTo: extension.attachTo,
disabled: extension.disabled,
extension: extension as Extension<unknown, unknown>,
plugin: undefined,
plugin: createFrontendPlugin({ pluginId: 'root' }),
...spec,
};
}
@@ -20,7 +20,7 @@ import {
Extension,
ExtensionDefinition,
} from '@backstage/frontend-plugin-api';
import { resolveAppNodeSpecs } from './resolveAppNodeSpecs';
import { resolveAppNodeSpecs, rootPlugin } from './resolveAppNodeSpecs';
function makeExt(
id: string,
@@ -62,12 +62,15 @@ describe('resolveAppNodeSpecs', () => {
builtinExtensions: [a],
parameters: [],
}),
).toEqual([
).toStrictEqual([
{
id: 'a',
extension: a,
attachTo: { id: 'root', input: 'default' },
disabled: true,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
]);
});
@@ -87,12 +90,18 @@ describe('resolveAppNodeSpecs', () => {
extension: a,
attachTo: { id: 'root', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
{
id: 'b',
extension: b,
attachTo: { id: 'root', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
]);
});
@@ -120,6 +129,9 @@ describe('resolveAppNodeSpecs', () => {
extension: b,
attachTo: { id: 'derp', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
{
id: 'test/a',
@@ -204,12 +216,18 @@ describe('resolveAppNodeSpecs', () => {
extension: b,
attachTo: { id: 'root', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
{
id: 'a',
extension: a,
attachTo: { id: 'root', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
]);
});
@@ -238,42 +256,63 @@ describe('resolveAppNodeSpecs', () => {
extension: e,
attachTo: { id: 'root', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
{
id: 'd',
extension: d,
attachTo: { id: 'root', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
{
id: 'c',
extension: c,
attachTo: { id: 'root', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
{
id: 'a',
extension: a,
attachTo: { id: 'root', input: 'default' },
disabled: true,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
{
id: 'b',
extension: b,
attachTo: { id: 'root', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
{
id: 'f',
extension: f,
attachTo: { id: 'root', input: 'default' },
disabled: false,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
{
id: 'g',
extension: g,
attachTo: { id: 'root', input: 'default' },
disabled: true,
config: undefined,
plugin: rootPlugin,
source: rootPlugin,
},
]);
});
@@ -14,7 +14,11 @@
* limitations under the License.
*/
import { Extension, FrontendFeature } from '@backstage/frontend-plugin-api';
import {
createFrontendPlugin,
Extension,
FrontendFeature,
} from '@backstage/frontend-plugin-api';
import { ExtensionParameters } from './readAppExtensionsConfig';
import { AppNodeSpec } from '@backstage/frontend-plugin-api';
import { OpaqueFrontendPlugin } from '@internal/frontend';
@@ -26,6 +30,8 @@ import {
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
export const rootPlugin = createFrontendPlugin({ pluginId: 'root' });
/** @internal */
export function resolveAppNodeSpecs(options: {
features?: FrontendFeature[];
@@ -106,8 +112,8 @@ export function resolveAppNodeSpecs(options: {
return {
extension: internalExtension,
params: {
source: undefined,
plugin: undefined,
source: rootPlugin,
plugin: rootPlugin,
attachTo: internalExtension.attachTo,
disabled: internalExtension.disabled,
config: undefined as unknown,
@@ -18,6 +18,7 @@ import {
coreExtensionData,
createExtension,
createExtensionInput,
createFrontendPlugin,
Extension,
} from '@backstage/frontend-plugin-api';
import { resolveAppTree } from './resolveAppTree';
@@ -37,6 +38,7 @@ const baseSpec = {
extension,
attachTo: { id: 'nonexistent', input: 'nonexistent' },
disabled: false,
plugin: createFrontendPlugin({ pluginId: 'root' }),
};
describe('buildAppTree', () => {
@@ -284,8 +286,20 @@ describe('buildAppTree', () => {
) as Extension<unknown, unknown>;
const tree = resolveAppTree('a', [
{ attachTo: e1.attachTo, id: 'a', extension: e1, disabled: false },
{ attachTo: e2.attachTo, id: 'b', extension: e2, disabled: false },
{
attachTo: e1.attachTo,
id: 'a',
extension: e1,
disabled: false,
plugin: baseSpec.plugin,
},
{
attachTo: e2.attachTo,
id: 'b',
extension: e2,
disabled: false,
plugin: baseSpec.plugin,
},
]);
expect(tree.root).toMatchInlineSnapshot(`
@@ -352,9 +366,27 @@ describe('buildAppTree', () => {
) as Extension<unknown, unknown>;
const tree = resolveAppTree('test-2', [
{ attachTo: e1.attachTo, id: e1.id, extension: e1, disabled: false },
{ attachTo: e2.attachTo, id: e2.id, extension: e2, disabled: false },
{ attachTo: e3.attachTo, id: e3.id, extension: e3, disabled: false },
{
attachTo: e1.attachTo,
id: e1.id,
extension: e1,
disabled: false,
plugin: baseSpec.plugin,
},
{
attachTo: e2.attachTo,
id: e2.id,
extension: e2,
disabled: false,
plugin: baseSpec.plugin,
},
{
attachTo: e3.attachTo,
id: e3.id,
extension: e3,
disabled: false,
plugin: baseSpec.plugin,
},
]);
expect(tree.nodes.get('test-3')?.edges.attachedTo?.node).toBe(
@@ -37,7 +37,7 @@ export interface AppNodeSpec {
readonly extension: Extension<unknown, unknown>;
readonly disabled: boolean;
readonly config?: unknown;
readonly plugin?: FrontendPlugin;
readonly plugin: FrontendPlugin;
}
/**