frontend-plugin-api: rename AppNodeSpec.source -> .plugin

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-05-16 09:49:01 +02:00
parent da2a66eac6
commit 173db8fcd6
12 changed files with 48 additions and 20 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
The `source` property of `AppNodeSpec` has been renamed to `plugin`. The old property has been deprecated and will be removed in a future release.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/frontend-app-api': patch
'@backstage/core-compat-api': patch
---
Updates to use the new `plugin` property of `AppNodeSpec`.
@@ -119,7 +119,7 @@ function LegacyAppContextProvider(props: { children: ReactNode }) {
const pluginSet = new Set<LegacyBackstagePlugin>();
for (const node of tree.nodes.values()) {
const plugin = node.spec.source;
const plugin = node.spec.plugin;
if (plugin) {
pluginSet.add(toLegacyPlugin(plugin));
}
@@ -49,7 +49,7 @@ describe('RouteTracker', () => {
appNode: {
spec: {
extension: { id: 'home.page.index' },
source: { id: 'home' },
plugin: { id: 'home' },
},
} as AppNode,
},
@@ -63,7 +63,7 @@ describe('RouteTracker', () => {
appNode: {
spec: {
extension: { id: 'plugin1.page.index' },
source: { id: 'plugin1' },
plugin: { id: 'plugin1' },
},
} as AppNode,
},
@@ -77,7 +77,7 @@ describe('RouteTracker', () => {
appNode: {
spec: {
extension: { id: 'plugin2.page.index' },
source: { id: 'plugin2' },
plugin: { id: 'plugin2' },
},
} as AppNode,
},
@@ -63,7 +63,7 @@ const getExtensionContext = (
return acc;
}, {});
const plugin = routeObject.appNode?.spec.source;
const plugin = routeObject.appNode?.spec.plugin;
const extension = routeObject.appNode?.spec.extension;
return {
@@ -124,8 +124,8 @@ export function extractRouteInfoFromAppNode(node: AppNode): {
routeParents.set(routeRef, newParentRef);
currentObj?.routeRefs.add(routeRef);
if (current.spec.source) {
currentObj?.plugins.add(toLegacyPlugin(current.spec.source));
if (current.spec.plugin) {
currentObj?.plugins.add(toLegacyPlugin(current.spec.plugin));
}
}
@@ -57,6 +57,7 @@ function makeSpec<TConfig, TConfigInput>(
disabled: extension.disabled,
extension: extension as Extension<unknown, unknown>,
source: undefined,
plugin: undefined,
...spec,
};
}
@@ -126,6 +126,7 @@ describe('resolveAppNodeSpecs', () => {
extension: makeExt('test/a'),
attachTo: { id: 'root', input: 'default' },
source: pluginA,
plugin: pluginA,
disabled: false,
},
]);
@@ -162,6 +163,7 @@ describe('resolveAppNodeSpecs', () => {
id: 'test/a',
extension: a,
attachTo: { id: 'root', input: 'default' },
plugin,
source: plugin,
config: { foo: { bar: 1 } },
disabled: false,
@@ -170,6 +172,7 @@ describe('resolveAppNodeSpecs', () => {
id: 'test/b',
extension: b,
attachTo: { id: 'root', input: 'default' },
plugin,
source: plugin,
config: { foo: { qux: 3 } },
disabled: false,
@@ -305,6 +308,7 @@ describe('resolveAppNodeSpecs', () => {
id: 'test/a',
extension: expect.objectContaining(aOverride),
attachTo: { id: 'other', input: 'default' },
plugin,
source: plugin,
disabled: false,
},
@@ -312,6 +316,7 @@ describe('resolveAppNodeSpecs', () => {
id: 'test/b',
extension: expect.objectContaining(bOverride),
attachTo: { id: 'other', input: 'default' },
plugin,
source: plugin,
disabled: true,
},
@@ -319,6 +324,7 @@ describe('resolveAppNodeSpecs', () => {
id: 'test/c',
extension: expect.objectContaining(cOverride),
attachTo: { id: 'root', input: 'default' },
plugin,
source: plugin,
disabled: false,
},
@@ -46,23 +46,23 @@ export function resolveAppNodeSpecs(options: {
const plugins = features.filter(OpaqueFrontendPlugin.isType);
const modules = features.filter(isInternalFrontendModule);
const pluginExtensions = plugins.flatMap(source => {
return OpaqueFrontendPlugin.toInternal(source).extensions.map(
const pluginExtensions = plugins.flatMap(plugin => {
return OpaqueFrontendPlugin.toInternal(plugin).extensions.map(
extension => ({
...extension,
source,
plugin,
}),
);
});
const moduleExtensions = modules.flatMap(mod =>
toInternalFrontendModule(mod).extensions.flatMap(extension => {
// Modules for plugins that are not installed are ignored
const source = plugins.find(p => p.id === mod.pluginId);
if (!source) {
const plugin = plugins.find(p => p.id === mod.pluginId);
if (!plugin) {
return [];
}
return [{ ...extension, source }];
return [{ ...extension, plugin }];
}),
);
@@ -70,7 +70,7 @@ export function resolveAppNodeSpecs(options: {
if (pluginExtensions.some(({ id }) => forbidden.has(id))) {
const pluginsStr = pluginExtensions
.filter(({ id }) => forbidden.has(id))
.map(({ source }) => `'${source.id}'`)
.map(({ plugin }) => `'${plugin.id}'`)
.join(', ');
const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', ');
throw new Error(
@@ -80,7 +80,7 @@ export function resolveAppNodeSpecs(options: {
if (moduleExtensions.some(({ id }) => forbidden.has(id))) {
const pluginsStr = moduleExtensions
.filter(({ id }) => forbidden.has(id))
.map(({ source }) => `'${source.id}'`)
.map(({ plugin }) => `'${plugin.id}'`)
.join(', ');
const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', ');
throw new Error(
@@ -89,12 +89,13 @@ export function resolveAppNodeSpecs(options: {
}
const configuredExtensions = [
...pluginExtensions.map(({ source, ...extension }) => {
...pluginExtensions.map(({ plugin, ...extension }) => {
const internalExtension = toInternalExtension(extension);
return {
extension: internalExtension,
params: {
source,
plugin,
source: plugin,
attachTo: internalExtension.attachTo,
disabled: internalExtension.disabled,
config: undefined as unknown,
@@ -107,6 +108,7 @@ export function resolveAppNodeSpecs(options: {
extension: internalExtension,
params: {
source: undefined,
plugin: undefined,
attachTo: internalExtension.attachTo,
disabled: internalExtension.disabled,
config: undefined as unknown,
@@ -133,7 +135,8 @@ export function resolveAppNodeSpecs(options: {
configuredExtensions.push({
extension: internalExtension,
params: {
source: extension.source,
plugin: extension.plugin,
source: extension.plugin,
attachTo: internalExtension.attachTo,
disabled: internalExtension.disabled,
config: undefined,
@@ -219,6 +222,7 @@ export function resolveAppNodeSpecs(options: {
attachTo: param.params.attachTo,
extension: param.extension,
disabled: param.params.disabled,
plugin: param.params.plugin,
source: param.params.source,
config: param.params.config,
}));
@@ -235,6 +235,8 @@ export interface AppNodeSpec {
// (undocumented)
readonly id: string;
// (undocumented)
readonly plugin?: FrontendPlugin;
// @deprecated (undocumented)
readonly source?: FrontendPlugin;
}
@@ -37,6 +37,10 @@ export interface AppNodeSpec {
readonly extension: Extension<unknown, unknown>;
readonly disabled: boolean;
readonly config?: unknown;
readonly plugin?: FrontendPlugin;
/**
* @deprecated Use {@link AppNodeSpec.plugin} instead.
*/
readonly source?: FrontendPlugin;
}
@@ -69,14 +69,14 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) {
node.instance?.getData(coreExtensionData.routePath),
);
const plugin = node.spec.source;
const plugin = node.spec.plugin;
const Progress = useComponentRef(coreComponentRefs.progress);
const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback);
// Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight
const attributes = {
extensionId: node.spec.id,
pluginId: node.spec.source?.id ?? 'app',
pluginId: node.spec.plugin?.id ?? 'app',
};
return (