From 860f6e7a3f9848b9789ca3f771772d503831338d Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 15:43:13 +0200 Subject: [PATCH] feat: introduced `createFrontendModule` and deprecating `createExtensionOverrides` Co-authored-by: Patrik Oldsberg Signed-off-by: blam --- .../src/collectLegacyRoutes.test.tsx | 6 +- .../src/convertLegacyPlugin.test.tsx | 7 +- .../src/routing/collectRouteIds.ts | 8 +- .../src/tree/resolveAppNodeSpecs.ts | 16 +-- .../src/wiring/createApp.test.tsx | 54 ++++++++ .../frontend-app-api/src/wiring/createApp.tsx | 30 ++++- .../src/wiring/createExtensionOverrides.ts | 10 +- .../src/wiring/createFrontendModule.ts | 127 ++++++++++++++++++ .../src/wiring/createFrontendPlugin.test.ts | 5 +- .../src/wiring/createFrontendPlugin.ts | 77 ++++++++--- .../frontend-plugin-api/src/wiring/index.ts | 8 +- .../frontend-plugin-api/src/wiring/types.ts | 23 +--- 12 files changed, 307 insertions(+), 64 deletions(-) create mode 100644 packages/frontend-plugin-api/src/wiring/createFrontendModule.ts diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx index b7b9f6781e..34d2937425 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx @@ -31,7 +31,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import { collectLegacyRoutes } from './collectLegacyRoutes'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalBackstagePlugin } from '../../frontend-plugin-api/src/wiring/createFrontendPlugin'; +import { toInternalFrontendPlugin } from '../../frontend-plugin-api/src/wiring/createFrontendPlugin'; import { createPlugin, createRoutableExtension, @@ -55,7 +55,7 @@ describe('collectLegacyRoutes', () => { expect( collected.map(p => ({ id: p.id, - extensions: toInternalBackstagePlugin(p).extensions.map(e => ({ + extensions: toInternalFrontendPlugin(p).extensions.map(e => ({ id: e.id, attachTo: e.attachTo, disabled: e.disabled, @@ -158,7 +158,7 @@ describe('collectLegacyRoutes', () => { expect( collected.map(p => ({ id: p.id, - extensions: toInternalBackstagePlugin(p).extensions.map(e => ({ + extensions: toInternalFrontendPlugin(p).extensions.map(e => ({ id: e.id, attachTo: e.attachTo, disabled: e.disabled, diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx index 657ee91725..3199355188 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx +++ b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx @@ -24,7 +24,7 @@ import { import { convertLegacyPlugin } from './convertLegacyPlugin'; import { PageBlueprint } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalBackstagePlugin } from '../../frontend-plugin-api/src/wiring/createFrontendPlugin'; +import { toInternalFrontendPlugin } from '../../frontend-plugin-api/src/wiring/createFrontendPlugin'; describe('convertLegacyPlugin', () => { it('should convert a plain legacy plugin to a new plugin', () => { @@ -34,12 +34,13 @@ describe('convertLegacyPlugin', () => { }), ).toMatchInlineSnapshot(` { - "$$type": "@backstage/BackstagePlugin", + "$$type": "@backstage/FrontendPlugin", "extensions": [], "externalRoutes": {}, "featureFlags": [], "getExtension": [Function], "id": "test", + "pluginId": "test", "routes": {}, "toString": [Function], "version": "v1", @@ -73,7 +74,7 @@ describe('convertLegacyPlugin', () => { }, ); - const internalConverted = toInternalBackstagePlugin(converted); + const internalConverted = toInternalFrontendPlugin(converted); expect(internalConverted.id).toBe('test'); expect(internalConverted.routes).toEqual({ diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.ts b/packages/frontend-app-api/src/routing/collectRouteIds.ts index d45871ae89..40f71d7c81 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.ts @@ -29,6 +29,8 @@ import { import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { isInternalFrontendPlugin } from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin'; /** @internal */ export interface RouteRefsById { @@ -42,12 +44,12 @@ export function collectRouteIds(features: FrontendFeature[]): RouteRefsById { const externalRoutesById = new Map(); for (const feature of features) { - if (feature.$$type !== '@backstage/BackstagePlugin') { + if (!isInternalFrontendPlugin(feature)) { continue; } for (const [name, ref] of Object.entries(feature.routes)) { - const refId = `${feature.id}.${name}`; + const refId = `${feature.pluginId}.${name}`; if (routesById.has(refId)) { throw new Error(`Unexpected duplicate route '${refId}'`); } @@ -62,7 +64,7 @@ export function collectRouteIds(features: FrontendFeature[]): RouteRefsById { } } for (const [name, ref] of Object.entries(feature.externalRoutes)) { - const refId = `${feature.id}.${name}`; + const refId = `${feature.pluginId}.${name}`; if (externalRoutesById.has(refId)) { throw new Error(`Unexpected duplicate external route '${refId}'`); } diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index b92485c5d6..8d73899018 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -15,7 +15,6 @@ */ import { - BackstagePlugin, Extension, ExtensionOverrides, FrontendFeature, @@ -25,7 +24,10 @@ import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/w import { ExtensionParameters } from './readAppExtensionsConfig'; import { AppNodeSpec } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin'; +import { + isInternalFrontendPlugin, + toInternalFrontendPlugin, +} from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; @@ -43,16 +45,14 @@ export function resolveAppNodeSpecs(options: { features = [], } = options; - const plugins = features.filter( - (f): f is BackstagePlugin => f.$$type === '@backstage/BackstagePlugin', - ); + const plugins = features.filter(isInternalFrontendPlugin); const overrides = features.filter( (f): f is ExtensionOverrides => f.$$type === '@backstage/ExtensionOverrides', ); const pluginExtensions = plugins.flatMap(source => { - return toInternalBackstagePlugin(source).extensions.map(extension => ({ + return toInternalFrontendPlugin(source).extensions.map(extension => ({ ...extension, source, })); @@ -65,7 +65,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(({ source }) => `'${source.pluginId}'`) .join(', '); const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', '); throw new Error( @@ -156,7 +156,7 @@ export function resolveAppNodeSpecs(options: { const extensionId = extension.id; const extensionData = data?.[extensionId]; if (extensionData) duplicatedExtensionIds.add(extensionId); - const pluginId = params.source?.id ?? 'internal'; + const pluginId = params.source?.pluginId ?? 'internal'; const pluginCount = extensionData?.[pluginId] ?? 0; return { ...data, diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx index 908298cb9a..1a92a0eb7d 100644 --- a/packages/frontend-app-api/src/wiring/createApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx @@ -23,6 +23,7 @@ import { PageBlueprint, createFrontendPlugin, ThemeBlueprint, + createFrontendModule, } from '@backstage/frontend-plugin-api'; import { screen, waitFor } from '@testing-library/react'; import { CreateAppFeatureLoader, createApp } from './createApp'; @@ -379,4 +380,57 @@ describe('createApp', () => { expect(screen.queryByText('Custom app root element')).toBeNull(); }); + + describe('modules', () => { + it('should be able to override extensions with a plugin extension override', async () => { + const mod = createFrontendModule({ + pluginId: 'app', + extensions: [ + appPlugin.getExtension('app/root').override({ + factory: () => [ + coreExtensionData.reactElement( +
Custom app root element
, + ), + ], + }), + ], + }); + + const app = createApp({ + configLoader: () => new Promise(() => {}), + features: [mod], + }); + + await renderWithEffects(app.createRoot()); + + expect(screen.queryByText('Custom app root element')).toBeNull(); + }); + + it('should be able to override extensions with a standalone extension override', async () => { + const mod = createFrontendModule({ + pluginId: 'app', + extensions: [ + createExtension({ + name: 'root', + attachTo: { id: 'app', input: 'root' }, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement( +
Custom app root element
, + ), + ], + }), + ], + }); + + const app = createApp({ + configLoader: () => new Promise(() => {}), + features: [mod], + }); + + await renderWithEffects(app.createRoot()); + + expect(screen.queryByText('Custom app root element')).toBeNull(); + }); + }); }); diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 37b7d2758a..95e688f104 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -61,7 +61,15 @@ import { RouteResolver } from '../routing/RouteResolver'; import { resolveRouteBindings } from '../routing/resolveRouteBindings'; import { collectRouteIds } from '../routing/collectRouteIds'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin'; +import { + toInternalFrontendPlugin, + isInternalFrontendPlugin, +} from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + toInternalFrontendModule, + isInternalFrontendModule, +} from '../../../frontend-plugin-api/src/wiring/createFrontendModule'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides'; import { stringifyError } from '@backstage/errors'; @@ -89,13 +97,13 @@ function deduplicateFeatures( return features .reverse() .filter(feature => { - if (feature.$$type !== '@backstage/BackstagePlugin') { + if (!isInternalFrontendPlugin(feature)) { return true; } - if (seenIds.has(feature.id)) { + if (seenIds.has(feature.pluginId)) { return false; } - seenIds.add(feature.id); + seenIds.add(feature.pluginId); return true; }) .reverse(); @@ -318,11 +326,19 @@ export function createSpecializedApp(options?: { const featureFlagApi = apiHolder.get(featureFlagsApiRef); if (featureFlagApi) { for (const feature of features) { - if (feature.$$type === '@backstage/BackstagePlugin') { - toInternalBackstagePlugin(feature).featureFlags.forEach(flag => + if (isInternalFrontendPlugin(feature)) { + toInternalFrontendPlugin(feature).featureFlags.forEach(flag => featureFlagApi.registerFlag({ name: flag.name, - pluginId: feature.id, + pluginId: feature.pluginId, + }), + ); + } + if (isInternalFrontendModule(feature)) { + toInternalFrontendModule(feature).featureFlags.forEach(flag => + featureFlagApi.registerFlag({ + name: flag.name, + pluginId: feature.pluginId, }), ); } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts index 5add7b79b9..887f9b8dab 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts @@ -21,7 +21,10 @@ import { } from './resolveExtensionDefinition'; import { ExtensionOverrides, FeatureFlagConfig } from './types'; -/** @public */ +/** + * @deprecated Use {@link createFrontendModule} instead. + * @public + */ export interface ExtensionOverridesOptions { extensions: ExtensionDefinition[]; featureFlags?: FeatureFlagConfig[]; @@ -34,7 +37,10 @@ export interface InternalExtensionOverrides extends ExtensionOverrides { readonly featureFlags: FeatureFlagConfig[]; } -/** @public */ +/** + * @deprecated Use {@link createFrontendModule} instead. + * @public + */ export function createExtensionOverrides( options: ExtensionOverridesOptions, ): ExtensionOverrides { diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts new file mode 100644 index 0000000000..ae444cb152 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts @@ -0,0 +1,127 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ExtensionDefinition, + InternalExtensionDefinition, + toInternalExtensionDefinition, +} from './createExtension'; +import { + Extension, + resolveExtensionDefinition, +} from './resolveExtensionDefinition'; +import { FeatureFlagConfig } from './types'; + +/** @public */ +export interface ModuleOptions< + TPluginId extends string, + TExtensions extends readonly ExtensionDefinition[], +> { + pluginId: TPluginId; + extensions?: TExtensions; + featureFlags?: FeatureFlagConfig[]; +} + +/** @public */ +export interface FrontendModule { + readonly $$type: '@backstage/FrontendModule'; + readonly pluginId: string; +} + +/** @internal */ +export interface InternalFrontendModule extends FrontendModule { + readonly version: 'v1'; + readonly extensions: Extension[]; + readonly featureFlags: FeatureFlagConfig[]; +} + +/** @public */ +export function createFrontendModule< + TId extends string, + TExtensions extends readonly ExtensionDefinition[] = [], +>(options: ModuleOptions): FrontendModule { + const { pluginId } = options; + + const extensions = new Array>(); + const extensionDefinitionsById = new Map< + string, + InternalExtensionDefinition + >(); + + for (const def of options.extensions ?? []) { + const internal = toInternalExtensionDefinition(def); + const ext = resolveExtensionDefinition(def, { namespace: pluginId }); + extensions.push(ext); + extensionDefinitionsById.set(ext.id, { + ...internal, + namespace: pluginId, + }); + } + + if (extensions.length !== extensionDefinitionsById.size) { + const extensionIds = extensions.map(e => e.id); + const duplicates = Array.from( + new Set( + extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index), + ), + ); + // TODO(Rugvip): This could provide some more information about the kind + name of the extensions + throw new Error( + `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join( + ', ', + )}`, + ); + } + + return { + $$type: '@backstage/FrontendModule', + version: 'v1', + pluginId, + featureFlags: options.featureFlags ?? [], + extensions, + toString() { + return `Module{pluginId=${pluginId}}`; + }, + } as InternalFrontendModule; +} + +/** @internal */ +export function isInternalFrontendModule(opaque: { + $$type: string; +}): opaque is InternalFrontendModule { + if (opaque.$$type === '@backstage/FrontendModule') { + // Make sure we throw if invalid + toInternalFrontendModule(opaque as FrontendModule); + return true; + } + return false; +} + +/** @internal */ +export function toInternalFrontendModule( + plugin: FrontendModule, +): InternalFrontendModule { + const internal = plugin as InternalFrontendModule; + if (internal.$$type !== '@backstage/FrontendModule') { + throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`); + } + if (internal.version !== 'v1') { + throw new Error( + `Invalid plugin instance, bad version '${internal.version}'`, + ); + } + return internal; +} diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts index 83ae085856..c461d6fb20 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts @@ -17,14 +17,13 @@ import React from 'react'; import { createApp } from '@backstage/frontend-app-api'; import { screen } from '@testing-library/react'; -import { createFrontendPlugin } from './createFrontendPlugin'; +import { FrontendPlugin, createFrontendPlugin } from './createFrontendPlugin'; import { JsonObject } from '@backstage/types'; import { createExtension } from './createExtension'; import { createExtensionDataRef } from './createExtensionDataRef'; import { coreExtensionData } from './coreExtensionData'; import { MockConfigApi, renderWithEffects } from '@backstage/test-utils'; import { createExtensionInput } from './createExtensionInput'; -import { BackstagePlugin } from './types'; const nameExtensionDataRef = createExtensionDataRef().with({ id: 'name', @@ -123,7 +122,7 @@ function createTestAppRoot({ features, config = {}, }: { - features: BackstagePlugin[]; + features: FrontendPlugin[]; config: JsonObject; }) { return createApp({ diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 860b5bf98a..8be515a577 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -24,12 +24,33 @@ import { ResolveExtensionId, resolveExtensionDefinition, } from './resolveExtensionDefinition'; -import { - AnyExternalRoutes, - AnyRoutes, - BackstagePlugin, - FeatureFlagConfig, -} from './types'; +import { AnyExternalRoutes, AnyRoutes, FeatureFlagConfig } from './types'; + +/** @public */ +export interface FrontendPlugin< + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TExtensionMap extends { [id in string]: ExtensionDefinition } = {}, +> { + readonly $$type: '@backstage/FrontendPlugin'; + readonly pluginId: string; + readonly routes: TRoutes; + readonly externalRoutes: TExternalRoutes; + getExtension(id: TId): TExtensionMap[TId]; + withOverrides(options: { + extensions: Array; + }): FrontendPlugin; +} + +/** + * @public + * @deprecated Use {@link FrontendPlugin} instead. + */ +export type BackstagePlugin< + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TExtensionMap extends { [id in string]: ExtensionDefinition } = {}, +> = FrontendPlugin; /** @public */ export interface PluginOptions< @@ -48,10 +69,10 @@ export interface PluginOptions< } /** @public */ -export interface InternalBackstagePlugin< +export interface InternalFrontendPlugin< TRoutes extends AnyRoutes = AnyRoutes, TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, -> extends BackstagePlugin { +> extends FrontendPlugin { readonly version: 'v1'; readonly extensions: Extension[]; readonly featureFlags: FeatureFlagConfig[]; @@ -65,7 +86,7 @@ export function createFrontendPlugin< TExtensions extends readonly ExtensionDefinition[] = [], >( options: PluginOptions, -): BackstagePlugin< +): FrontendPlugin< TRoutes, TExternalRoutes, { @@ -112,9 +133,9 @@ export function createFrontendPlugin< } return { - $$type: '@backstage/BackstagePlugin', + $$type: '@backstage/FrontendPlugin', version: 'v1', - id: pluginId, + id: pluginId, // TODO: Backwards compat to be able to install in older apps, remove after 1.31 pluginId, routes: options.routes ?? ({} as TRoutes), externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), @@ -143,15 +164,33 @@ export function createFrontendPlugin< extensions: [...nonOverriddenExtensions, ...overrides.extensions], }); }, - } as InternalBackstagePlugin; + } as InternalFrontendPlugin; } /** @internal */ -export function toInternalBackstagePlugin( - plugin: BackstagePlugin, -): InternalBackstagePlugin { - const internal = plugin as InternalBackstagePlugin; - if (internal.$$type !== '@backstage/BackstagePlugin') { +export function isInternalFrontendPlugin(opaque: { + $$type: string; +}): opaque is InternalFrontendPlugin { + if ( + opaque.$$type === '@backstage/FrontendPlugin' || + opaque.$$type === '@backstage/BackstagePlugin' + ) { + // Throw if invalid + mutate + toInternalFrontendPlugin(opaque as FrontendPlugin); + return true; + } + return false; +} + +/** @internal */ +export function toInternalFrontendPlugin( + plugin: FrontendPlugin, +): InternalFrontendPlugin { + const internal = plugin as InternalFrontendPlugin; + if ( + internal.$$type !== '@backstage/FrontendPlugin' && + internal.$$type !== '@backstage/BackstagePlugin' + ) { throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`); } if (internal.version !== 'v1') { @@ -159,6 +198,10 @@ export function toInternalBackstagePlugin( `Invalid plugin instance, bad version '${internal.version}'`, ); } + // Backwards compatibility to support old plugins defined with just an .id + if (!internal.pluginId) { + (internal as any).pluginId = (internal as any).id; + } return internal; } diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 6e136a6514..4954d8e72d 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -39,8 +39,15 @@ export { export { createPlugin, createFrontendPlugin, + type FrontendPlugin, + type BackstagePlugin, type PluginOptions, } from './createFrontendPlugin'; +export { + createFrontendModule, + type FrontendModule, + type ModuleOptions, +} from './createFrontendModule'; export { createExtensionOverrides, type ExtensionOverridesOptions, @@ -49,7 +56,6 @@ export { type Extension } from './resolveExtensionDefinition'; export { type AnyRoutes, type AnyExternalRoutes, - type BackstagePlugin, type ExtensionOverrides, type FeatureFlagConfig, type FrontendFeature, diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index b713b2d926..04400e4211 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -16,6 +16,8 @@ import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing'; import { ExtensionDefinition } from './createExtension'; +import { FrontendModule } from './createFrontendModule'; +import { FrontendPlugin } from './createFrontendPlugin'; /** * Feature flag configuration. @@ -40,26 +42,13 @@ export type ExtensionMap< get(id: TId): TExtensionMap[TId]; }; -/** @public */ -export interface BackstagePlugin< - TRoutes extends AnyRoutes = AnyRoutes, - TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, - TExtensionMap extends { [id in string]: ExtensionDefinition } = {}, -> { - readonly $$type: '@backstage/BackstagePlugin'; - readonly id: string; - readonly routes: TRoutes; - readonly externalRoutes: TExternalRoutes; - getExtension(id: TId): TExtensionMap[TId]; - withOverrides(options: { - extensions: Array; - }): BackstagePlugin; -} - /** @public */ export interface ExtensionOverrides { readonly $$type: '@backstage/ExtensionOverrides'; } /** @public */ -export type FrontendFeature = BackstagePlugin | ExtensionOverrides; +export type FrontendFeature = + | FrontendPlugin + | FrontendModule + | ExtensionOverrides;