frontend-plugin-api: remove type support for multiple attachment points

This is a follow-up to the deprecation in #32521, completely removing
the Array variant from the ExtensionDefinitionAttachTo type.

The runtime still supports multiple attachment points for backward
compatibility, but new code will receive type errors.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-02 14:52:55 +01:00
parent 38ea4541e4
commit 10ebed46b1
10 changed files with 41 additions and 73 deletions
+1 -20
View File
@@ -1205,26 +1205,7 @@ export type ExtensionDefinitionAttachTo<
input: string;
id?: never;
}
| ExtensionInput<UParentInputs>
/**
* @deprecated Multiple attachment points are deprecated and will be removed in a future release. Use a Utility API instead to share functionality across multiple locations. See https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions for migration guidance.
*/
| Array<
| {
id: string;
input: string;
relative?: never;
}
| {
relative: {
kind?: string;
name?: string;
};
input: string;
id?: never;
}
| ExtensionInput<UParentInputs>
>;
| ExtensionInput<UParentInputs>;
// @public (undocumented)
export type ExtensionDefinitionParameters = {
@@ -32,12 +32,10 @@ describe('AnalyticsBlueprint', () => {
{
"$$type": "@backstage/ExtensionDefinition",
"T": undefined,
"attachTo": [
{
"id": "api:app/analytics",
"input": "implementations",
},
],
"attachTo": {
"id": "api:app/analytics",
"input": "implementations",
},
"configSchema": undefined,
"disabled": false,
"factory": [Function],
@@ -41,7 +41,7 @@ const factoryDataRef =
*/
export const AnalyticsImplementationBlueprint = createExtensionBlueprint({
kind: 'analytics',
attachTo: [{ id: 'api:app/analytics', input: 'implementations' }],
attachTo: { id: 'api:app/analytics', input: 'implementations' },
output: [factoryDataRef],
dataRefs: {
factory: factoryDataRef,
@@ -202,12 +202,14 @@ describe('createExtension', () => {
});
});
it('should create an extension with multiple attachment points', () => {
// Tests for backward compatibility - runtime still supports multiple attachment points
// but the TypeScript types no longer allow them
it('should create an extension with multiple attachment points (backward compat)', () => {
const extension = createExtension({
attachTo: [
{ id: 'root', input: 'default' },
{ id: 'other', input: 'default' },
],
] as any,
output: [stringDataRef, numberDataRef.optional()],
factory: () => [stringDataRef('bar')],
});
@@ -216,14 +218,14 @@ describe('createExtension', () => {
);
});
it('should create an extension with relative attachment points', () => {
it('should create an extension with relative attachment points (backward compat)', () => {
const extension = createExtension({
attachTo: [
{ relative: {}, input: 'tabs' },
{ relative: { kind: 'page' }, input: 'tabs' },
{ relative: { name: 'index' }, input: 'tabs' },
{ relative: { kind: 'page', name: 'index' }, input: 'tabs' },
],
] as any,
output: [stringDataRef],
factory: () => [stringDataRef('bar')],
});
@@ -232,7 +234,7 @@ describe('createExtension', () => {
);
});
it('should create an extension with relative attachment points by reference', () => {
it('should create an extension with relative attachment points by reference (backward compat)', () => {
const baseOpts = {
attachTo: { id: 'root', input: 'children' },
inputs: {
@@ -269,7 +271,7 @@ describe('createExtension', () => {
parent2.inputs.tabs,
parent3.inputs.tabs,
parent4.inputs.otherTabs,
],
] as any,
output: [stringDataRef],
factory: () => [stringDataRef('bar')],
});
@@ -281,7 +283,7 @@ describe('createExtension', () => {
parent2.inputs.tabs,
parent3.inputs.tabs,
parent4.inputs.otherTabs,
],
] as any,
});
expect(String(overrdeExtension)).toBe(
'ExtensionDefinition{attachTo=page:<plugin>@tabs+<plugin>/index@tabs+page:<plugin>/index@otherTabs}',
@@ -134,11 +134,10 @@ export type VerifyExtensionAttachTo<
*
* A standard attachment point declaration will specify the ID of the parent extension, as well as the name of the input to attach to.
*
* There are three more advanced forms that are available for more complex use-cases:
* There are two more advanced forms that are available for more complex use-cases:
*
* 1. Relative attachment points: using the `relative` property instead of `id`, the attachment point is resolved relative to the current plugin.
* 2. Extension input references: using a reference in code to another extension's input in the same plugin. These references are always relative.
* 3. Array of attachment points: an array of attachment points can be used to clone and attach to multiple extensions at once.
*
* @example
* ```ts
@@ -151,12 +150,6 @@ export type VerifyExtensionAttachTo<
* // Attach to a specific input of another extension
* const page = ParentBlueprint.make({ ... });
* const child = ChildBlueprint.make({ attachTo: page.inputs.children });
*
* // Attach to multiple parents at once (deprecated - use Utility APIs instead)
* [
* { id: 'page/home', input: 'widgets' },
* { relative: { kind: 'page' }, input: 'widgets' },
* ]
* ```
*
* @public
@@ -166,19 +159,7 @@ export type ExtensionDefinitionAttachTo<
> =
| { id: string; input: string; relative?: never }
| { relative: { kind?: string; name?: string }; input: string; id?: never }
| ExtensionInput<UParentInputs>
/**
* @deprecated Multiple attachment points are deprecated and will be removed in a future release. Use a Utility API instead to share functionality across multiple locations. See https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions for migration guidance.
*/
| Array<
| { id: string; input: string; relative?: never }
| {
relative: { kind?: string; name?: string };
input: string;
id?: never;
}
| ExtensionInput<UParentInputs>
>;
| ExtensionInput<UParentInputs>;
/** @public */
export type CreateExtensionOptions<
@@ -92,11 +92,7 @@ describe('createExtensionBlueprint', () => {
it('should allow creation of extension blueprints with a generator', () => {
const TestExtensionBlueprint = createExtensionBlueprint({
kind: 'test-extension',
// Try multiple attachment points for this one
attachTo: [
{ id: 'test-1', input: 'default' },
{ id: 'test-2', input: 'default' },
],
attachTo: { id: 'test-1', input: 'default' },
output: [coreExtensionData.reactElement],
*factory(params: { text: string }) {
yield coreExtensionData.reactElement(<h1>{params.text}</h1>);
@@ -112,10 +108,7 @@ describe('createExtensionBlueprint', () => {
expect(extension).toEqual({
$$type: '@backstage/ExtensionDefinition',
attachTo: [
{ id: 'test-1', input: 'default' },
{ id: 'test-2', input: 'default' },
],
attachTo: { id: 'test-1', input: 'default' },
configSchema: undefined,
disabled: false,
inputs: {},
@@ -1345,7 +1338,9 @@ describe('createExtensionBlueprint', () => {
).toThrow('Refused to override params and factory at the same time');
});
describe('with relative attachment points', () => {
// Tests for backward compatibility - runtime still supports multiple attachment points
// but the TypeScript types no longer allow them
describe('with relative attachment points (backward compat)', () => {
const dataRef = createExtensionDataRef<string>().with({ id: 'test.data' });
it('should create an extension with relative attachment points', () => {
@@ -1356,7 +1351,7 @@ describe('createExtensionBlueprint', () => {
{ relative: { kind: 'page' }, input: 'tabs' },
{ relative: { name: 'index' }, input: 'tabs' },
{ relative: { kind: 'page', name: 'index' }, input: 'tabs' },
],
] as any,
output: [dataRef],
factory: () => [dataRef('bar')],
});
@@ -1371,7 +1366,7 @@ describe('createExtensionBlueprint', () => {
{ relative: { kind: 'page' }, input: 'tabs' },
{ relative: { name: 'index' }, input: 'tabs' },
{ relative: { kind: 'page', name: 'index' }, input: 'tabs' },
],
] as any,
params: {},
}),
),
@@ -1431,7 +1426,7 @@ describe('createExtensionBlueprint', () => {
parent2.inputs.tabs,
parent3.inputs.tabs,
parent4.inputs.otherTabs,
],
] as any,
output: [dataRef],
factory: () => [dataRef('bar')],
});
@@ -1445,7 +1440,7 @@ describe('createExtensionBlueprint', () => {
parent2.inputs.tabs,
parent3.inputs.tabs,
parent4.inputs.otherTabs,
],
] as any,
params: {},
}),
),
@@ -140,6 +140,7 @@ describe('resolveExtensionDefinition', () => {
input: 'children',
});
// Test for backward compatibility - runtime still supports multiple attachment points
expect(
resolveExtensionDefinition(
OpaqueExtensionDefinition.toInternal({
@@ -157,7 +158,7 @@ describe('resolveExtensionDefinition', () => {
kind: 'k3',
input: 'children',
}),
],
] as any,
}),
{ namespace: 'test' },
).attachTo,
@@ -149,11 +149,11 @@ function resolveExtensionId(
}
function resolveAttachTo(
attachTo: ExtensionDefinitionAttachTo,
attachTo: ExtensionDefinitionAttachTo | ExtensionDefinitionAttachTo[],
namespace?: string,
): ExtensionAttachToSpec {
const resolveSpec = (
spec: Exclude<ExtensionDefinitionAttachTo, Array<any>>,
spec: ExtensionDefinitionAttachTo,
): { id: string; input: string } => {
if (OpaqueExtensionInput.isType(spec)) {
const { context } = OpaqueExtensionInput.toInternal(spec);