diff --git a/.changeset/remove-array-attachto-type.md b/.changeset/remove-array-attachto-type.md new file mode 100644 index 0000000000..99b3513bc9 --- /dev/null +++ b/.changeset/remove-array-attachto-type.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed type support for multiple attachment points in the `ExtensionDefinitionAttachTo` type. Extensions can no longer specify an array of attachment points in the `attachTo` property. + +The runtime still supports multiple attachment points for backward compatibility with existing compiled code, but new code will receive type errors if attempting to use this pattern. + +Extensions that previously used multiple attachment points should migrate to using a Utility API pattern instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended approach. diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index 6c79002ad9..e21befc80e 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -648,12 +648,13 @@ describe('createSpecializedApp', () => { output: [coreExtensionData.reactElement], factory: () => [coreExtensionData.reactElement(
)], }), + // Test backward compatibility - runtime still supports multiple attachment points createExtension({ name: 'cloned', attachTo: [ { id: 'test/a', input: 'children' }, { id: 'test/b', input: 'children' }, - ], + ] as any, output: [coreExtensionData.reactElement], factory: () => [coreExtensionData.reactElement(
)], }), diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 80e2ff8df4..89fc72992a 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -1205,26 +1205,7 @@ export type ExtensionDefinitionAttachTo< input: string; id?: never; } - | ExtensionInput - /** - * @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 - >; + | ExtensionInput; // @public (undocumented) export type ExtensionDefinitionParameters = { diff --git a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts index b297b3a3a9..06ad9b877e 100644 --- a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts @@ -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], diff --git a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts index 91a0f27092..23b5982738 100644 --- a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts @@ -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, diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 989dcf4468..8f259be6f9 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -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:@tabs+/index@tabs+page:/index@otherTabs}', diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index c1f704f61a..4d3eeece2c 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -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 - /** - * @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 - >; + | ExtensionInput; /** @public */ export type CreateExtensionOptions< diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 4759441187..7f3ef5cb88 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -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(

{params.text}

); @@ -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().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: {}, }), ), diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index 79749af77c..e080ab1f64 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -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, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index f99d586e87..605e75bfe8 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -149,11 +149,11 @@ function resolveExtensionId( } function resolveAttachTo( - attachTo: ExtensionDefinitionAttachTo, + attachTo: ExtensionDefinitionAttachTo | ExtensionDefinitionAttachTo[], namespace?: string, ): ExtensionAttachToSpec { const resolveSpec = ( - spec: Exclude>, + spec: ExtensionDefinitionAttachTo, ): { id: string; input: string } => { if (OpaqueExtensionInput.isType(spec)) { const { context } = OpaqueExtensionInput.toInternal(spec);