From 6780a49d3639bd1bd832850f02df89c6dbae17ef Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 14:03:52 +0200 Subject: [PATCH 01/12] chore: deprecate `id` from `createFrontendPlugin` Signed-off-by: blam --- .../src/wiring/createFrontendPlugin.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 2bea1fe8be..860b5bf98a 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -38,7 +38,9 @@ export interface PluginOptions< TExternalRoutes extends AnyExternalRoutes, TExtensions extends readonly ExtensionDefinition[], > { - id: TId; + /** @deprecated use `pluginId` instead */ + id?: TId; + pluginId?: TId; routes?: TRoutes; externalRoutes?: TExternalRoutes; extensions?: TExtensions; @@ -73,6 +75,11 @@ export function createFrontendPlugin< >]: KExtension; } > { + const pluginId = options.pluginId ?? options.id; + if (!pluginId) { + throw new Error('No pluginId provided to createFrontendPlugin'); + } + const extensions = new Array>(); const extensionDefinitionsById = new Map< string, @@ -81,11 +88,11 @@ export function createFrontendPlugin< for (const def of options.extensions ?? []) { const internal = toInternalExtensionDefinition(def); - const ext = resolveExtensionDefinition(def, { namespace: options.id }); + const ext = resolveExtensionDefinition(def, { namespace: pluginId }); extensions.push(ext); extensionDefinitionsById.set(ext.id, { ...internal, - namespace: options.id, + namespace: pluginId, }); } @@ -98,7 +105,7 @@ export function createFrontendPlugin< ); // TODO(Rugvip): This could provide some more information about the kind + name of the extensions throw new Error( - `Plugin '${options.id}' provided duplicate extensions: ${duplicates.join( + `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join( ', ', )}`, ); @@ -107,7 +114,8 @@ export function createFrontendPlugin< return { $$type: '@backstage/BackstagePlugin', version: 'v1', - id: options.id, + id: pluginId, + pluginId, routes: options.routes ?? ({} as TRoutes), externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), featureFlags: options.featureFlags ?? [], @@ -116,18 +124,18 @@ export function createFrontendPlugin< return extensionDefinitionsById.get(id); }, toString() { - return `Plugin{id=${options.id}}`; + return `Plugin{id=${pluginId}}`; }, withOverrides(overrides) { const overriddenExtensionIds = new Set( overrides.extensions.map( - e => resolveExtensionDefinition(e, { namespace: options.id }).id, + e => resolveExtensionDefinition(e, { namespace: pluginId }).id, ), ); const nonOverriddenExtensions = (options.extensions ?? []).filter( e => !overriddenExtensionIds.has( - resolveExtensionDefinition(e, { namespace: options.id }).id, + resolveExtensionDefinition(e, { namespace: pluginId }).id, ), ); return createFrontendPlugin({ From c1ef48f4fb17edb3d758c0e3a98a237982d5e4b8 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 14:05:34 +0200 Subject: [PATCH 02/12] chore: added changeset Signed-off-by: blam --- .changeset/cuddly-suits-agree.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .changeset/cuddly-suits-agree.md diff --git a/.changeset/cuddly-suits-agree.md b/.changeset/cuddly-suits-agree.md new file mode 100644 index 0000000000..a82ac8fd44 --- /dev/null +++ b/.changeset/cuddly-suits-agree.md @@ -0,0 +1,14 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Deprecate the `id` in `createFrontendPlugin` in favour of `pluginId` to match the same as the backend. + +```ts +const myPlugin = createFrontendPlugin({ + pluginId: 'my-plugin', + extensions: [ + ... + ] +}); +``` From 860f6e7a3f9848b9789ca3f771772d503831338d Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 15:43:13 +0200 Subject: [PATCH 03/12] 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; From c816e2d35a5583542204a775211d58f5fa1de401 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 16:14:28 +0200 Subject: [PATCH 04/12] chore: updating changeset Signed-off-by: blam --- .changeset/friendly-days-march.md | 7 +++++++ .changeset/friendly-months-march.md | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 .changeset/friendly-days-march.md create mode 100644 .changeset/friendly-months-march.md diff --git a/.changeset/friendly-days-march.md b/.changeset/friendly-days-march.md new file mode 100644 index 0000000000..d3170cee4e --- /dev/null +++ b/.changeset/friendly-days-march.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added `createFrontendModule` as a replacement for `createExtensionOverrides`, which is now deprecated. + +Deprecated the `BackstagePlugin` type in favor of `FrontendPlugin`. diff --git a/.changeset/friendly-months-march.md b/.changeset/friendly-months-march.md new file mode 100644 index 0000000000..0bb9cd7cf0 --- /dev/null +++ b/.changeset/friendly-months-march.md @@ -0,0 +1,6 @@ +--- +'@backstage/frontend-app-api': patch +'@backstage/core-compat-api': patch +--- + +Added support for new `FrontendPlugin` and `FrontendModule` types. From 8a1ed67ba254ab23fdc5d1e944d501a925447165 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 16:15:06 +0200 Subject: [PATCH 05/12] chore: updating api-reports Signed-off-by: blam --- .../app-next-example-plugin/api-report.md | 4 +- packages/frontend-plugin-api/api-report.md | 83 ++++++++++++++----- plugins/api-docs/api-report-alpha.md | 4 +- plugins/app-visualizer/api-report.md | 4 +- plugins/app/api-report.md | 4 +- plugins/catalog-graph/api-report-alpha.md | 4 +- plugins/catalog-import/api-report-alpha.md | 4 +- plugins/catalog/api-report-alpha.md | 4 +- plugins/devtools/api-report-alpha.md | 4 +- plugins/home/api-report-alpha.md | 4 +- plugins/kubernetes/api-report-alpha.md | 4 +- plugins/org/api-report-alpha.md | 4 +- plugins/scaffolder/api-report-alpha.md | 4 +- plugins/search/api-report-alpha.md | 4 +- plugins/techdocs/api-report-alpha.md | 4 +- plugins/user-settings/api-report-alpha.md | 4 +- 16 files changed, 91 insertions(+), 52 deletions(-) diff --git a/packages/app-next-example-plugin/api-report.md b/packages/app-next-example-plugin/api-report.md index fe2afb81a7..06d325b844 100644 --- a/packages/app-next-example-plugin/api-report.md +++ b/packages/app-next-example-plugin/api-report.md @@ -4,14 +4,14 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const examplePlugin: BackstagePlugin< +const examplePlugin: FrontendPlugin< {}, {}, { diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 9c1907775b..7d1f44d995 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -320,29 +320,14 @@ export { BackstageIdentityApi }; export { BackstageIdentityResponse }; -// @public (undocumented) -export interface BackstagePlugin< +// @public @deprecated (undocumented) +export type BackstagePlugin< TRoutes extends AnyRoutes = AnyRoutes, TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, TExtensionMap extends { [id in string]: ExtensionDefinition; } = {}, -> { - // (undocumented) - readonly $$type: '@backstage/BackstagePlugin'; - // (undocumented) - readonly externalRoutes: TExternalRoutes; - // (undocumented) - getExtension(id: TId): TExtensionMap[TId]; - // (undocumented) - readonly id: string; - // (undocumented) - readonly routes: TRoutes; - // (undocumented) - withOverrides(options: { - extensions: Array; - }): BackstagePlugin; -} +> = FrontendPlugin; export { BackstageUserIdentity }; @@ -732,7 +717,7 @@ export type CreateExtensionOptions< }): Iterable; } & VerifyExtensionFactoryOutput; -// @public (undocumented) +// @public @deprecated (undocumented) export function createExtensionOverrides( options: ExtensionOverridesOptions, ): ExtensionOverrides; @@ -760,6 +745,12 @@ export function createExternalRouteRef< } >; +// @public (undocumented) +export function createFrontendModule< + TId extends string, + TExtensions extends readonly ExtensionDefinition[] = [], +>(options: ModuleOptions): FrontendModule; + // @public (undocumented) export function createFrontendPlugin< TId extends string, @@ -768,7 +759,7 @@ export function createFrontendPlugin< TExtensions extends readonly ExtensionDefinition[] = [], >( options: PluginOptions, -): BackstagePlugin< +): FrontendPlugin< TRoutes, TExternalRoutes, { @@ -1204,7 +1195,7 @@ export interface ExtensionOverrides { readonly $$type: '@backstage/ExtensionOverrides'; } -// @public (undocumented) +// @public @deprecated (undocumented) export interface ExtensionOverridesOptions { // (undocumented) extensions: ExtensionDefinition[]; @@ -1242,7 +1233,42 @@ export { FetchApi }; export { fetchApiRef }; // @public (undocumented) -export type FrontendFeature = BackstagePlugin | ExtensionOverrides; +export type FrontendFeature = + | FrontendPlugin + | FrontendModule + | ExtensionOverrides; + +// @public (undocumented) +export interface FrontendModule { + // (undocumented) + readonly $$type: '@backstage/FrontendModule'; + // (undocumented) + readonly pluginId: string; +} + +// @public (undocumented) +export interface FrontendPlugin< + TRoutes extends AnyRoutes = AnyRoutes, + TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TExtensionMap extends { + [id in string]: ExtensionDefinition; + } = {}, +> { + // (undocumented) + readonly $$type: '@backstage/FrontendPlugin'; + // (undocumented) + readonly externalRoutes: TExternalRoutes; + // (undocumented) + getExtension(id: TId): TExtensionMap[TId]; + // (undocumented) + readonly id: string; + // (undocumented) + readonly routes: TRoutes; + // (undocumented) + withOverrides(options: { + extensions: Array; + }): FrontendPlugin; +} export { githubAuthApiRef }; @@ -1314,6 +1340,19 @@ export { identityApiRef }; export { microsoftAuthApiRef }; +// @public (undocumented) +export interface ModuleOptions< + TPluginId extends string, + TExtensions extends readonly ExtensionDefinition[], +> { + // (undocumented) + extensions?: TExtensions; + // (undocumented) + featureFlags?: FeatureFlagConfig[]; + // (undocumented) + pluginId: TPluginId; +} + // @public export const NavItemBlueprint: ExtensionBlueprint<{ kind: 'nav-item'; diff --git a/plugins/api-docs/api-report-alpha.md b/plugins/api-docs/api-report-alpha.md index 70bf57ebc0..bb2da7bc0b 100644 --- a/plugins/api-docs/api-report-alpha.md +++ b/plugins/api-docs/api-report-alpha.md @@ -6,18 +6,18 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { root: RouteRef; }, diff --git a/plugins/app-visualizer/api-report.md b/plugins/app-visualizer/api-report.md index 7bf5e3608b..e542674845 100644 --- a/plugins/app-visualizer/api-report.md +++ b/plugins/app-visualizer/api-report.md @@ -4,15 +4,15 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const visualizerPlugin: BackstagePlugin< +const visualizerPlugin: FrontendPlugin< {}, {}, { diff --git a/plugins/app/api-report.md b/plugins/app/api-report.md index 73f5076ef3..ca76ecb91b 100644 --- a/plugins/app/api-report.md +++ b/plugins/app/api-report.md @@ -9,12 +9,12 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { AppTheme } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ComponentRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -25,7 +25,7 @@ import { TranslationMessages } from '@backstage/frontend-plugin-api'; import { TranslationResource } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const appPlugin: BackstagePlugin< +const appPlugin: FrontendPlugin< {}, {}, { diff --git a/plugins/catalog-graph/api-report-alpha.md b/plugins/catalog-graph/api-report-alpha.md index 6e1027dea0..65f8af3764 100644 --- a/plugins/catalog-graph/api-report-alpha.md +++ b/plugins/catalog-graph/api-report-alpha.md @@ -5,18 +5,18 @@ ```ts import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Direction } from '@backstage/plugin-catalog-graph'; import { Entity } from '@backstage/catalog-model'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { catalogGraph: RouteRef; }, diff --git a/plugins/catalog-import/api-report-alpha.md b/plugins/catalog-import/api-report-alpha.md index 992de9fc54..3eed9ce3e4 100644 --- a/plugins/catalog-import/api-report-alpha.md +++ b/plugins/catalog-import/api-report-alpha.md @@ -5,14 +5,14 @@ ```ts import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { importPage: RouteRef; }, diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 151794858a..62e631dd4a 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -8,13 +8,13 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -123,7 +123,7 @@ export const catalogTranslationRef: TranslationRef< >; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { catalogIndex: RouteRef; catalogEntity: RouteRef<{ diff --git a/plugins/devtools/api-report-alpha.md b/plugins/devtools/api-report-alpha.md index a6a121ebd7..7fb16734f1 100644 --- a/plugins/devtools/api-report-alpha.md +++ b/plugins/devtools/api-report-alpha.md @@ -5,15 +5,15 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { root: RouteRef; }, diff --git a/plugins/home/api-report-alpha.md b/plugins/home/api-report-alpha.md index 7347d98d7b..7b4b157eb2 100644 --- a/plugins/home/api-report-alpha.md +++ b/plugins/home/api-report-alpha.md @@ -4,15 +4,15 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< {}, {}, { diff --git a/plugins/kubernetes/api-report-alpha.md b/plugins/kubernetes/api-report-alpha.md index b0cad703d6..852a5c6c1a 100644 --- a/plugins/kubernetes/api-report-alpha.md +++ b/plugins/kubernetes/api-report-alpha.md @@ -7,15 +7,15 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { kubernetes: RouteRef; }, diff --git a/plugins/org/api-report-alpha.md b/plugins/org/api-report-alpha.md index c50e28bc7d..a8c6bd70ec 100644 --- a/plugins/org/api-report-alpha.md +++ b/plugins/org/api-report-alpha.md @@ -3,15 +3,15 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< {}, { catalogIndex: ExternalRouteRef; diff --git a/plugins/scaffolder/api-report-alpha.md b/plugins/scaffolder/api-report-alpha.md index 7758cc59d2..555ab2ac13 100644 --- a/plugins/scaffolder/api-report-alpha.md +++ b/plugins/scaffolder/api-report-alpha.md @@ -5,13 +5,13 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; import { PathParams } from '@backstage/core-plugin-api'; @@ -24,7 +24,7 @@ import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { root: RouteRef; selectedTemplate: SubRouteRef< diff --git a/plugins/search/api-report-alpha.md b/plugins/search/api-report-alpha.md index 9e043ab792..fe06873bd4 100644 --- a/plugins/search/api-report-alpha.md +++ b/plugins/search/api-report-alpha.md @@ -5,10 +5,10 @@ ```ts import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -16,7 +16,7 @@ import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-rea import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { root: RouteRef; }, diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index 881778d5ae..6a22a9fce9 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -6,11 +6,11 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -18,7 +18,7 @@ import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-rea import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { root: RouteRef; docRoot: RouteRef<{ diff --git a/plugins/user-settings/api-report-alpha.md b/plugins/user-settings/api-report-alpha.md index c8e320e7d0..199dbba582 100644 --- a/plugins/user-settings/api-report-alpha.md +++ b/plugins/user-settings/api-report-alpha.md @@ -4,17 +4,17 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) -const _default: BackstagePlugin< +const _default: FrontendPlugin< { root: RouteRef; }, From fe0046150a09a7d741c8a441ad0d63160787502e Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 16:15:20 +0200 Subject: [PATCH 06/12] chore: some renames and supporting both formats Signed-off-by: blam --- .../compatWrapper/BackwardsCompatProvider.tsx | 8 ++--- .../src/routing/RouteTracker.test.tsx | 5 ++- .../src/routing/collectRouteIds.ts | 4 +-- .../src/tree/resolveAppNodeSpecs.ts | 4 +-- .../frontend-app-api/src/wiring/createApp.tsx | 6 ++-- .../src/wiring/createFrontendPlugin.ts | 32 ++++++------------- 6 files changed, 25 insertions(+), 34 deletions(-) diff --git a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx index 445f42124f..0f9ccfb2ad 100644 --- a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx +++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx @@ -22,7 +22,7 @@ import { AppContextProvider } from '../../../core-app-api/src/app/AppContext'; import { RouteResolver } from '../../../core-plugin-api/src/routing/useRouteRef'; import { createFrontendPlugin as createNewPlugin, - BackstagePlugin as NewBackstagePlugin, + FrontendPlugin as NewFrontendPlugin, appTreeApiRef, componentsApiRef, coreComponentRefs, @@ -47,11 +47,11 @@ import { convertLegacyRouteRef } from '../convertLegacyRouteRef'; // Make sure that we only convert each new plugin instance to its legacy equivalent once const legacyPluginStore = getOrCreateGlobalSingleton( 'legacy-plugin-compatibility-store', - () => new WeakMap(), + () => new WeakMap(), ); export function toLegacyPlugin( - plugin: NewBackstagePlugin, + plugin: NewFrontendPlugin, ): LegacyBackstagePlugin { let legacy = legacyPluginStore.get(plugin); if (legacy) { @@ -83,7 +83,7 @@ export function toLegacyPlugin( } // TODO: Currently a very naive implementation, may need some more work -function toNewPlugin(plugin: LegacyBackstagePlugin): NewBackstagePlugin { +function toNewPlugin(plugin: LegacyBackstagePlugin): NewFrontendPlugin { return createNewPlugin({ id: plugin.getId(), }); diff --git a/packages/frontend-app-api/src/routing/RouteTracker.test.tsx b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx index 935449b697..aeec03992f 100644 --- a/packages/frontend-app-api/src/routing/RouteTracker.test.tsx +++ b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx @@ -46,7 +46,10 @@ describe('RouteTracker', () => { caseSensitive: false, children: [MATCH_ALL_ROUTE], appNode: { - spec: { extension: { id: 'home.page.index' }, source: { id: 'home' } }, + spec: { + extension: { id: 'home.page.index' }, + source: { id: 'home' }, + }, } as AppNode, }, { diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.ts b/packages/frontend-app-api/src/routing/collectRouteIds.ts index 40f71d7c81..6e9e1a6ebb 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.ts @@ -49,7 +49,7 @@ export function collectRouteIds(features: FrontendFeature[]): RouteRefsById { } for (const [name, ref] of Object.entries(feature.routes)) { - const refId = `${feature.pluginId}.${name}`; + const refId = `${feature.id}.${name}`; if (routesById.has(refId)) { throw new Error(`Unexpected duplicate route '${refId}'`); } @@ -64,7 +64,7 @@ export function collectRouteIds(features: FrontendFeature[]): RouteRefsById { } } for (const [name, ref] of Object.entries(feature.externalRoutes)) { - const refId = `${feature.pluginId}.${name}`; + const refId = `${feature.id}.${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 8d73899018..f210bd1186 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -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.pluginId}'`) + .map(({ source }) => `'${source.id}'`) .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?.pluginId ?? 'internal'; + const pluginId = params.source?.id ?? 'internal'; const pluginCount = extensionData?.[pluginId] ?? 0; return { ...data, diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 95e688f104..40535ae09c 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -100,10 +100,10 @@ function deduplicateFeatures( if (!isInternalFrontendPlugin(feature)) { return true; } - if (seenIds.has(feature.pluginId)) { + if (seenIds.has(feature.id)) { return false; } - seenIds.add(feature.pluginId); + seenIds.add(feature.id); return true; }) .reverse(); @@ -330,7 +330,7 @@ export function createSpecializedApp(options?: { toInternalFrontendPlugin(feature).featureFlags.forEach(flag => featureFlagApi.registerFlag({ name: flag.name, - pluginId: feature.pluginId, + pluginId: feature.id, }), ); } diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 8be515a577..3d8c318354 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -33,7 +33,7 @@ export interface FrontendPlugin< TExtensionMap extends { [id in string]: ExtensionDefinition } = {}, > { readonly $$type: '@backstage/FrontendPlugin'; - readonly pluginId: string; + readonly id: string; readonly routes: TRoutes; readonly externalRoutes: TExternalRoutes; getExtension(id: TId): TExtensionMap[TId]; @@ -59,9 +59,7 @@ export interface PluginOptions< TExternalRoutes extends AnyExternalRoutes, TExtensions extends readonly ExtensionDefinition[], > { - /** @deprecated use `pluginId` instead */ - id?: TId; - pluginId?: TId; + id: TId; routes?: TRoutes; externalRoutes?: TExternalRoutes; extensions?: TExtensions; @@ -96,11 +94,6 @@ export function createFrontendPlugin< >]: KExtension; } > { - const pluginId = options.pluginId ?? options.id; - if (!pluginId) { - throw new Error('No pluginId provided to createFrontendPlugin'); - } - const extensions = new Array>(); const extensionDefinitionsById = new Map< string, @@ -109,11 +102,11 @@ export function createFrontendPlugin< for (const def of options.extensions ?? []) { const internal = toInternalExtensionDefinition(def); - const ext = resolveExtensionDefinition(def, { namespace: pluginId }); + const ext = resolveExtensionDefinition(def, { namespace: options.id }); extensions.push(ext); extensionDefinitionsById.set(ext.id, { ...internal, - namespace: pluginId, + namespace: options.id, }); } @@ -126,7 +119,7 @@ export function createFrontendPlugin< ); // 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( + `Plugin '${options.id}' provided duplicate extensions: ${duplicates.join( ', ', )}`, ); @@ -135,8 +128,7 @@ export function createFrontendPlugin< return { $$type: '@backstage/FrontendPlugin', version: 'v1', - id: pluginId, // TODO: Backwards compat to be able to install in older apps, remove after 1.31 - pluginId, + id: options.id, // TODO: Backwards compat to be able to install in older apps, remove after 1.31 routes: options.routes ?? ({} as TRoutes), externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), featureFlags: options.featureFlags ?? [], @@ -145,18 +137,18 @@ export function createFrontendPlugin< return extensionDefinitionsById.get(id); }, toString() { - return `Plugin{id=${pluginId}}`; + return `Plugin{id=${options.id}}`; }, withOverrides(overrides) { const overriddenExtensionIds = new Set( overrides.extensions.map( - e => resolveExtensionDefinition(e, { namespace: pluginId }).id, + e => resolveExtensionDefinition(e, { namespace: options.id }).id, ), ); const nonOverriddenExtensions = (options.extensions ?? []).filter( e => !overriddenExtensionIds.has( - resolveExtensionDefinition(e, { namespace: pluginId }).id, + resolveExtensionDefinition(e, { namespace: options.id }).id, ), ); return createFrontendPlugin({ @@ -175,7 +167,7 @@ export function isInternalFrontendPlugin(opaque: { opaque.$$type === '@backstage/FrontendPlugin' || opaque.$$type === '@backstage/BackstagePlugin' ) { - // Throw if invalid + mutate + // Make sure we throw if invalid toInternalFrontendPlugin(opaque as FrontendPlugin); return true; } @@ -198,10 +190,6 @@ export function toInternalFrontendPlugin( `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; } From f8c623c686d426d9469880bcf3d7f4474cc259b4 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 16:18:27 +0200 Subject: [PATCH 07/12] chore: remove uneccessary changeset Signed-off-by: blam --- .changeset/cuddly-suits-agree.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .changeset/cuddly-suits-agree.md diff --git a/.changeset/cuddly-suits-agree.md b/.changeset/cuddly-suits-agree.md deleted file mode 100644 index a82ac8fd44..0000000000 --- a/.changeset/cuddly-suits-agree.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Deprecate the `id` in `createFrontendPlugin` in favour of `pluginId` to match the same as the backend. - -```ts -const myPlugin = createFrontendPlugin({ - pluginId: 'my-plugin', - extensions: [ - ... - ] -}); -``` From 686d6c2f515e0b7a652d75bb0838832a2e8b4383 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 16:48:44 +0200 Subject: [PATCH 08/12] chore: add additional types to the feature detection guad Signed-off-by: blam --- packages/frontend-app-api/src/wiring/discovery.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/frontend-app-api/src/wiring/discovery.ts b/packages/frontend-app-api/src/wiring/discovery.ts index 4e50290350..f80bf47aa8 100644 --- a/packages/frontend-app-api/src/wiring/discovery.ts +++ b/packages/frontend-app-api/src/wiring/discovery.ts @@ -84,6 +84,10 @@ export function getAvailableFeatures(config: Config): FrontendFeature[] { function isBackstageFeature(obj: unknown): obj is FrontendFeature { if (obj !== null && typeof obj === 'object' && '$$type' in obj) { return ( + obj.$$type === '@backstage/FrontendPlugin' || + obj.$$type === '@backstage/FrontendModule' || + // TODO: Remove this once the old plugin type and extension overrides + // are no longer supported obj.$$type === '@backstage/BackstagePlugin' || obj.$$type === '@backstage/ExtensionOverrides' ); From d103f216b09c17fb60310cc6945ea8b6fbd9279b Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 27 Aug 2024 17:08:16 +0200 Subject: [PATCH 09/12] chore: cleanup Signed-off-by: blam --- packages/core-compat-api/src/convertLegacyPlugin.test.tsx | 1 - packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx index 3199355188..3d5b001c61 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx +++ b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx @@ -40,7 +40,6 @@ describe('convertLegacyPlugin', () => { "featureFlags": [], "getExtension": [Function], "id": "test", - "pluginId": "test", "routes": {}, "toString": [Function], "version": "v1", diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 3d8c318354..185eabb985 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -128,7 +128,7 @@ export function createFrontendPlugin< return { $$type: '@backstage/FrontendPlugin', version: 'v1', - id: options.id, // TODO: Backwards compat to be able to install in older apps, remove after 1.31 + id: options.id, routes: options.routes ?? ({} as TRoutes), externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), featureFlags: options.featureFlags ?? [], From f1e08f7dd8917bd382111667159e014b3a5cfcf2 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 28 Aug 2024 08:52:27 +0200 Subject: [PATCH 10/12] chore: created a frontend module Signed-off-by: blam --- .../src/wiring/createFrontendModule.test.ts | 62 +++++++++++++++++++ .../src/wiring/createFrontendModule.ts | 4 +- .../frontend-plugin-api/src/wiring/index.ts | 2 +- 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts new file mode 100644 index 0000000000..d895f6de0f --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.test.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2024 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 { createExtension } from './createExtension'; +import { createFrontendModule } from './createFrontendModule'; + +describe('createFrontendModule', () => { + it('should create a frontend module', () => { + expect( + createFrontendModule({ + pluginId: 'test', + extensions: [ + createExtension({ + kind: 'route', + name: 'test', + output: [], + attachTo: { id: 'ignored', input: 'ignored' }, + factory: () => [], + }), + ], + }), + ).toMatchInlineSnapshot(` + { + "$$type": "@backstage/FrontendModule", + "extensions": [ + { + "$$type": "@backstage/Extension", + "T": undefined, + "attachTo": { + "id": "ignored", + "input": "ignored", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "id": "route:test/test", + "inputs": {}, + "output": [], + "toString": [Function], + "version": "v2", + }, + ], + "featureFlags": [], + "pluginId": "test", + "toString": [Function], + "version": "v1", + } + `); + }); +}); diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts index ae444cb152..15e195f56d 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts @@ -26,7 +26,7 @@ import { import { FeatureFlagConfig } from './types'; /** @public */ -export interface ModuleOptions< +export interface FrontendModuleOptions< TPluginId extends string, TExtensions extends readonly ExtensionDefinition[], > { @@ -52,7 +52,7 @@ export interface InternalFrontendModule extends FrontendModule { export function createFrontendModule< TId extends string, TExtensions extends readonly ExtensionDefinition[] = [], ->(options: ModuleOptions): FrontendModule { +>(options: FrontendModuleOptions): FrontendModule { const { pluginId } = options; const extensions = new Array>(); diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 4954d8e72d..5cbeeb3c24 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -46,7 +46,7 @@ export { export { createFrontendModule, type FrontendModule, - type ModuleOptions, + type FrontendModuleOptions, } from './createFrontendModule'; export { createExtensionOverrides, From 3530a64deb8bfcf8eacae9691c8d2aef0a29d57c Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 28 Aug 2024 10:06:05 +0200 Subject: [PATCH 11/12] chore: api-reports Signed-off-by: blam --- packages/frontend-plugin-api/api-report.md | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 7d1f44d995..6eb5a87109 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -749,7 +749,7 @@ export function createExternalRouteRef< export function createFrontendModule< TId extends string, TExtensions extends readonly ExtensionDefinition[] = [], ->(options: ModuleOptions): FrontendModule; +>(options: FrontendModuleOptions): FrontendModule; // @public (undocumented) export function createFrontendPlugin< @@ -1246,6 +1246,19 @@ export interface FrontendModule { readonly pluginId: string; } +// @public (undocumented) +export interface FrontendModuleOptions< + TPluginId extends string, + TExtensions extends readonly ExtensionDefinition[], +> { + // (undocumented) + extensions?: TExtensions; + // (undocumented) + featureFlags?: FeatureFlagConfig[]; + // (undocumented) + pluginId: TPluginId; +} + // @public (undocumented) export interface FrontendPlugin< TRoutes extends AnyRoutes = AnyRoutes, @@ -1340,19 +1353,6 @@ export { identityApiRef }; export { microsoftAuthApiRef }; -// @public (undocumented) -export interface ModuleOptions< - TPluginId extends string, - TExtensions extends readonly ExtensionDefinition[], -> { - // (undocumented) - extensions?: TExtensions; - // (undocumented) - featureFlags?: FeatureFlagConfig[]; - // (undocumented) - pluginId: TPluginId; -} - // @public export const NavItemBlueprint: ExtensionBlueprint<{ kind: 'nav-item'; From e0ca667207c33c95309971ba7f24d81ce8009226 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 28 Aug 2024 11:55:05 +0200 Subject: [PATCH 12/12] chore: smol updates from pr review Signed-off-by: blam --- .changeset/friendly-days-march.md | 2 +- packages/core-compat-api/api-report.md | 5 +-- .../src/collectLegacyRoutes.tsx | 5 ++- .../core-compat-api/src/convertLegacyApp.ts | 5 +-- packages/frontend-app-api/api-report.md | 14 +++++++- .../src/routing/collectRouteIds.ts | 2 +- .../src/tree/resolveAppNodeSpecs.ts | 7 ++-- .../frontend-app-api/src/wiring/createApp.tsx | 2 +- .../frontend-app-api/src/wiring/discovery.ts | 2 +- packages/frontend-app-api/src/wiring/index.ts | 1 + packages/frontend-app-api/src/wiring/types.ts | 24 +++++++++++++ packages/frontend-plugin-api/api-report.md | 35 +++++++++---------- .../src/wiring/createFrontendModule.ts | 4 +-- .../src/wiring/createFrontendPlugin.ts | 1 - .../frontend-plugin-api/src/wiring/index.ts | 2 +- .../frontend-plugin-api/src/wiring/types.ts | 11 +++--- packages/frontend-test-utils/api-report.md | 2 +- .../src/app/renderInTestApp.tsx | 6 ++-- 18 files changed, 83 insertions(+), 47 deletions(-) create mode 100644 packages/frontend-app-api/src/wiring/types.ts diff --git a/.changeset/friendly-days-march.md b/.changeset/friendly-days-march.md index d3170cee4e..a190c50a43 100644 --- a/.changeset/friendly-days-march.md +++ b/.changeset/friendly-days-march.md @@ -4,4 +4,4 @@ Added `createFrontendModule` as a replacement for `createExtensionOverrides`, which is now deprecated. -Deprecated the `BackstagePlugin` type in favor of `FrontendPlugin`. +Deprecated the `BackstagePlugin` and `FrontendFeature` type in favor of `FrontendPlugin` and `FrontendFeature` from `@backstage/frontend-app-api` respectively. diff --git a/packages/core-compat-api/api-report.md b/packages/core-compat-api/api-report.md index 9e72d6178c..e61ee451d9 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -12,9 +12,10 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionOverrides } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api'; -import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -28,7 +29,7 @@ export function compatWrapper(element: ReactNode): React_2.JSX.Element; // @public (undocumented) export function convertLegacyApp( rootElement: React_2.JSX.Element, -): FrontendFeature[]; +): (FrontendPlugin | ExtensionOverrides)[]; // @public (undocumented) export function convertLegacyPageExtension( diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index 4c0e7efceb..197c245278 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -268,7 +268,10 @@ export function collectLegacyRoutes( extensions: [ ...extensions, ...Array.from(plugin.getApis()).map(factory => - ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }), + ApiBlueprint.make({ + namespace: factory.api.id, + params: { factory }, + }), ), ], routes: convertLegacyRouteRefs(plugin.routes ?? {}), diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts index ffeab3bdc6..2729664941 100644 --- a/packages/core-compat-api/src/convertLegacyApp.ts +++ b/packages/core-compat-api/src/convertLegacyApp.ts @@ -22,7 +22,8 @@ import React, { isValidElement, } from 'react'; import { - FrontendFeature, + ExtensionOverrides, + FrontendPlugin, coreExtensionData, createExtension, createExtensionInput, @@ -60,7 +61,7 @@ function selectChildren( /** @public */ export function convertLegacyApp( rootElement: React.JSX.Element, -): FrontendFeature[] { +): (FrontendPlugin | ExtensionOverrides)[] { if (getComponentData(rootElement, 'core.type') === 'FlatRoutes') { return collectLegacyRoutes(rootElement); } diff --git a/packages/frontend-app-api/api-report.md b/packages/frontend-app-api/api-report.md index c837ff09b7..e8fa7572b7 100644 --- a/packages/frontend-app-api/api-report.md +++ b/packages/frontend-app-api/api-report.md @@ -5,7 +5,8 @@ ```ts import { ConfigApi } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; -import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { FrontendModule } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -52,4 +53,15 @@ export function createSpecializedApp(options?: { }): { createRoot(): JSX_2.Element; }; + +// @public (undocumented) +export type FrontendFeature = + | FrontendPlugin + | FrontendModule + | { + $$type: '@backstage/ExtensionOverrides'; + } + | { + $$type: '@backstage/BackstagePlugin'; + }; ``` diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.ts b/packages/frontend-app-api/src/routing/collectRouteIds.ts index 6e9e1a6ebb..d89834c02e 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.ts @@ -18,7 +18,6 @@ import { RouteRef, SubRouteRef, ExternalRouteRef, - FrontendFeature, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { @@ -31,6 +30,7 @@ import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/rou 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'; +import { FrontendFeature } from '../wiring'; /** @internal */ export interface RouteRefsById { diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index f210bd1186..1ccc7bae7f 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - Extension, - ExtensionOverrides, - FrontendFeature, -} from '@backstage/frontend-plugin-api'; +import { Extension, ExtensionOverrides } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides'; import { ExtensionParameters } from './readAppExtensionsConfig'; @@ -30,6 +26,7 @@ import { } 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'; +import { FrontendFeature } from '../wiring'; /** @internal */ export function resolveAppNodeSpecs(options: { diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 40535ae09c..591dee9a00 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -22,7 +22,6 @@ import { AppTreeApi, appTreeApiRef, coreExtensionData, - FrontendFeature, RouteRef, ExternalRouteRef, SubRouteRef, @@ -85,6 +84,7 @@ import { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry'; import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; import { BackstageRouteObject } from '../routing/types'; import appPlugin from '@backstage/plugin-app'; +import { FrontendFeature } from './types'; function deduplicateFeatures( allFeatures: FrontendFeature[], diff --git a/packages/frontend-app-api/src/wiring/discovery.ts b/packages/frontend-app-api/src/wiring/discovery.ts index f80bf47aa8..6f9d2e2d16 100644 --- a/packages/frontend-app-api/src/wiring/discovery.ts +++ b/packages/frontend-app-api/src/wiring/discovery.ts @@ -15,7 +15,7 @@ */ import { Config, ConfigReader } from '@backstage/config'; -import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { FrontendFeature } from '@backstage/frontend-app-api'; interface DiscoveryGlobal { modules: Array<{ name: string; export?: string; default: unknown }>; diff --git a/packages/frontend-app-api/src/wiring/index.ts b/packages/frontend-app-api/src/wiring/index.ts index d1498514c8..bcf623d740 100644 --- a/packages/frontend-app-api/src/wiring/index.ts +++ b/packages/frontend-app-api/src/wiring/index.ts @@ -19,3 +19,4 @@ export { createSpecializedApp, type CreateAppFeatureLoader, } from './createApp'; +export * from './types'; diff --git a/packages/frontend-app-api/src/wiring/types.ts b/packages/frontend-app-api/src/wiring/types.ts new file mode 100644 index 0000000000..fb887534e3 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/types.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2024 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 { FrontendModule, FrontendPlugin } from '@backstage/frontend-plugin-api'; + +/** @public */ +export type FrontendFeature = + | FrontendPlugin + | FrontendModule + // TODO(blam): This is just forwards backwards compatibility, remove after v1.31.0 + | { $$type: '@backstage/ExtensionOverrides' } + | { $$type: '@backstage/BackstagePlugin' }; diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 6eb5a87109..b42efddffa 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -749,7 +749,20 @@ export function createExternalRouteRef< export function createFrontendModule< TId extends string, TExtensions extends readonly ExtensionDefinition[] = [], ->(options: FrontendModuleOptions): FrontendModule; +>(options: CreateFrontendModuleOptions): FrontendModule; + +// @public (undocumented) +export interface CreateFrontendModuleOptions< + TPluginId extends string, + TExtensions extends readonly ExtensionDefinition[], +> { + // (undocumented) + extensions?: TExtensions; + // (undocumented) + featureFlags?: FeatureFlagConfig[]; + // (undocumented) + pluginId: TPluginId; +} // @public (undocumented) export function createFrontendPlugin< @@ -1232,11 +1245,8 @@ export { FetchApi }; export { fetchApiRef }; -// @public (undocumented) -export type FrontendFeature = - | FrontendPlugin - | FrontendModule - | ExtensionOverrides; +// @public @deprecated (undocumented) +export type FrontendFeature = FrontendPlugin | ExtensionOverrides; // @public (undocumented) export interface FrontendModule { @@ -1246,19 +1256,6 @@ export interface FrontendModule { readonly pluginId: string; } -// @public (undocumented) -export interface FrontendModuleOptions< - TPluginId extends string, - TExtensions extends readonly ExtensionDefinition[], -> { - // (undocumented) - extensions?: TExtensions; - // (undocumented) - featureFlags?: FeatureFlagConfig[]; - // (undocumented) - pluginId: TPluginId; -} - // @public (undocumented) export interface FrontendPlugin< TRoutes extends AnyRoutes = AnyRoutes, diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts index 15e195f56d..aacc34ccf0 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts @@ -26,7 +26,7 @@ import { import { FeatureFlagConfig } from './types'; /** @public */ -export interface FrontendModuleOptions< +export interface CreateFrontendModuleOptions< TPluginId extends string, TExtensions extends readonly ExtensionDefinition[], > { @@ -52,7 +52,7 @@ export interface InternalFrontendModule extends FrontendModule { export function createFrontendModule< TId extends string, TExtensions extends readonly ExtensionDefinition[] = [], ->(options: FrontendModuleOptions): FrontendModule { +>(options: CreateFrontendModuleOptions): FrontendModule { const { pluginId } = options; const extensions = new Array>(); diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 185eabb985..03c843543d 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -51,7 +51,6 @@ export type BackstagePlugin< TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, TExtensionMap extends { [id in string]: ExtensionDefinition } = {}, > = FrontendPlugin; - /** @public */ export interface PluginOptions< TId extends string, diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 5cbeeb3c24..f8ad362bac 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -46,7 +46,7 @@ export { export { createFrontendModule, type FrontendModule, - type FrontendModuleOptions, + type CreateFrontendModuleOptions, } from './createFrontendModule'; export { createExtensionOverrides, diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index 04400e4211..b52bbf446c 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -16,7 +16,6 @@ import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing'; import { ExtensionDefinition } from './createExtension'; -import { FrontendModule } from './createFrontendModule'; import { FrontendPlugin } from './createFrontendPlugin'; /** @@ -47,8 +46,8 @@ export interface ExtensionOverrides { readonly $$type: '@backstage/ExtensionOverrides'; } -/** @public */ -export type FrontendFeature = - | FrontendPlugin - | FrontendModule - | ExtensionOverrides; +/** + * @public + * @deprecated import from {@link @backstage/frontend-app-api#FrontendFeature} instead + */ +export type FrontendFeature = FrontendPlugin | ExtensionOverrides; diff --git a/packages/frontend-test-utils/api-report.md b/packages/frontend-test-utils/api-report.md index ba4b8aae2b..10b84b0981 100644 --- a/packages/frontend-test-utils/api-report.md +++ b/packages/frontend-test-utils/api-report.md @@ -14,7 +14,7 @@ import { ErrorWithContext } from '@backstage/test-utils'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionDefinitionParameters } from '@backstage/frontend-plugin-api'; -import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { FrontendFeature } from '@backstage/frontend-app-api'; import { JsonObject } from '@backstage/types'; import { MockConfigApi } from '@backstage/test-utils'; import { MockErrorApi } from '@backstage/test-utils'; diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index 7923050050..fae67a260c 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -16,7 +16,10 @@ import React from 'react'; import { Link, MemoryRouter } from 'react-router-dom'; -import { createSpecializedApp } from '@backstage/frontend-app-api'; +import { + createSpecializedApp, + FrontendFeature, +} from '@backstage/frontend-app-api'; import { RenderResult, render } from '@testing-library/react'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; @@ -30,7 +33,6 @@ import { IconComponent, RouterBlueprint, NavItemBlueprint, - FrontendFeature, } from '@backstage/frontend-plugin-api'; import appPlugin from '@backstage/plugin-app';