Merge pull request #26245 from backstage/blam/namespace-be-gone

NFS: Introduce `createFrontendModule` as a replacement for `createExtensionOverrides`
This commit is contained in:
Patrik Oldsberg
2024-08-28 15:04:39 +02:00
committed by GitHub
42 changed files with 526 additions and 131 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/frontend-plugin-api': patch
---
Added `createFrontendModule` as a replacement for `createExtensionOverrides`, which is now deprecated.
Deprecated the `BackstagePlugin` and `FrontendFeature` type in favor of `FrontendPlugin` and `FrontendFeature` from `@backstage/frontend-app-api` respectively.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/frontend-app-api': patch
'@backstage/core-compat-api': patch
---
Added support for new `FrontendPlugin` and `FrontendModule` types.
@@ -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<
{},
{},
{
+3 -2
View File
@@ -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(
@@ -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,
@@ -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 ?? {}),
@@ -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<NewBackstagePlugin, LegacyBackstagePlugin>(),
() => new WeakMap<NewFrontendPlugin, LegacyBackstagePlugin>(),
);
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(),
});
@@ -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);
}
@@ -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,7 +34,7 @@ describe('convertLegacyPlugin', () => {
}),
).toMatchInlineSnapshot(`
{
"$$type": "@backstage/BackstagePlugin",
"$$type": "@backstage/FrontendPlugin",
"extensions": [],
"externalRoutes": {},
"featureFlags": [],
@@ -73,7 +73,7 @@ describe('convertLegacyPlugin', () => {
},
);
const internalConverted = toInternalBackstagePlugin(converted);
const internalConverted = toInternalFrontendPlugin(converted);
expect(internalConverted.id).toBe('test');
expect(internalConverted.routes).toEqual({
+13 -1
View File
@@ -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';
};
```
@@ -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,
},
{
@@ -18,7 +18,6 @@ import {
RouteRef,
SubRouteRef,
ExternalRouteRef,
FrontendFeature,
} from '@backstage/frontend-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import {
@@ -29,6 +28,9 @@ 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';
import { FrontendFeature } from '../wiring';
/** @internal */
export interface RouteRefsById {
@@ -42,7 +44,7 @@ export function collectRouteIds(features: FrontendFeature[]): RouteRefsById {
const externalRoutesById = new Map<string, ExternalRouteRef>();
for (const feature of features) {
if (feature.$$type !== '@backstage/BackstagePlugin') {
if (!isInternalFrontendPlugin(feature)) {
continue;
}
@@ -14,20 +14,19 @@
* limitations under the License.
*/
import {
BackstagePlugin,
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';
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';
import { FrontendFeature } from '../wiring';
/** @internal */
export function resolveAppNodeSpecs(options: {
@@ -43,16 +42,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,
}));
@@ -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(
<div>Custom app root element</div>,
),
],
}),
],
});
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(
<div>Custom app root element</div>,
),
],
}),
],
});
const app = createApp({
configLoader: () => new Promise(() => {}),
features: [mod],
});
await renderWithEffects(app.createRoot());
expect(screen.queryByText('Custom app root element')).toBeNull();
});
});
});
@@ -22,7 +22,6 @@ import {
AppTreeApi,
appTreeApiRef,
coreExtensionData,
FrontendFeature,
RouteRef,
ExternalRouteRef,
SubRouteRef,
@@ -61,7 +60,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';
@@ -77,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[],
@@ -89,7 +97,7 @@ function deduplicateFeatures(
return features
.reverse()
.filter(feature => {
if (feature.$$type !== '@backstage/BackstagePlugin') {
if (!isInternalFrontendPlugin(feature)) {
return true;
}
if (seenIds.has(feature.id)) {
@@ -318,14 +326,22 @@ 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,
}),
);
}
if (isInternalFrontendModule(feature)) {
toInternalFrontendModule(feature).featureFlags.forEach(flag =>
featureFlagApi.registerFlag({
name: flag.name,
pluginId: feature.pluginId,
}),
);
}
if (feature.$$type === '@backstage/ExtensionOverrides') {
toInternalExtensionOverrides(feature).featureFlags.forEach(flag =>
featureFlagApi.registerFlag({ name: flag.name, pluginId: '' }),
@@ -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 }>;
@@ -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'
);
@@ -19,3 +19,4 @@ export {
createSpecializedApp,
type CreateAppFeatureLoader,
} from './createApp';
export * from './types';
@@ -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' };
+58 -22
View File
@@ -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<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
// (undocumented)
readonly id: string;
// (undocumented)
readonly routes: TRoutes;
// (undocumented)
withOverrides(options: {
extensions: Array<ExtensionDefinition>;
}): BackstagePlugin<TRoutes, TExternalRoutes, TExtensionMap>;
}
> = FrontendPlugin<TRoutes, TExternalRoutes, TExtensionMap>;
export { BackstageUserIdentity };
@@ -732,7 +717,7 @@ export type CreateExtensionOptions<
}): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
// @public (undocumented)
// @public @deprecated (undocumented)
export function createExtensionOverrides(
options: ExtensionOverridesOptions,
): ExtensionOverrides;
@@ -760,6 +745,25 @@ export function createExternalRouteRef<
}
>;
// @public (undocumented)
export function createFrontendModule<
TId extends string,
TExtensions extends readonly ExtensionDefinition[] = [],
>(options: CreateFrontendModuleOptions<TId, TExtensions>): 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<
TId extends string,
@@ -768,7 +772,7 @@ export function createFrontendPlugin<
TExtensions extends readonly ExtensionDefinition[] = [],
>(
options: PluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>,
): BackstagePlugin<
): FrontendPlugin<
TRoutes,
TExternalRoutes,
{
@@ -1204,7 +1208,7 @@ export interface ExtensionOverrides {
readonly $$type: '@backstage/ExtensionOverrides';
}
// @public (undocumented)
// @public @deprecated (undocumented)
export interface ExtensionOverridesOptions {
// (undocumented)
extensions: ExtensionDefinition[];
@@ -1241,8 +1245,40 @@ export { FetchApi };
export { fetchApiRef };
// @public @deprecated (undocumented)
export type FrontendFeature = FrontendPlugin | ExtensionOverrides;
// @public (undocumented)
export type FrontendFeature = BackstagePlugin | ExtensionOverrides;
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<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
// (undocumented)
readonly id: string;
// (undocumented)
readonly routes: TRoutes;
// (undocumented)
withOverrides(options: {
extensions: Array<ExtensionDefinition>;
}): FrontendPlugin<TRoutes, TExternalRoutes, TExtensionMap>;
}
export { githubAuthApiRef };
@@ -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 {
@@ -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",
}
`);
});
});
@@ -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 CreateFrontendModuleOptions<
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<unknown>[];
readonly featureFlags: FeatureFlagConfig[];
}
/** @public */
export function createFrontendModule<
TId extends string,
TExtensions extends readonly ExtensionDefinition[] = [],
>(options: CreateFrontendModuleOptions<TId, TExtensions>): FrontendModule {
const { pluginId } = options;
const extensions = new Array<Extension<any>>();
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;
}
@@ -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<string>().with({
id: 'name',
@@ -123,7 +122,7 @@ function createTestAppRoot({
features,
config = {},
}: {
features: BackstagePlugin[];
features: FrontendPlugin[];
config: JsonObject;
}) {
return createApp({
@@ -24,13 +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 id: string;
readonly routes: TRoutes;
readonly externalRoutes: TExternalRoutes;
getExtension<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
withOverrides(options: {
extensions: Array<ExtensionDefinition>;
}): FrontendPlugin<TRoutes, TExternalRoutes, TExtensionMap>;
}
/**
* @public
* @deprecated Use {@link FrontendPlugin} instead.
*/
export type BackstagePlugin<
TRoutes extends AnyRoutes = AnyRoutes,
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
TExtensionMap extends { [id in string]: ExtensionDefinition } = {},
> = FrontendPlugin<TRoutes, TExternalRoutes, TExtensionMap>;
/** @public */
export interface PluginOptions<
TId extends string,
@@ -46,10 +66,10 @@ export interface PluginOptions<
}
/** @public */
export interface InternalBackstagePlugin<
export interface InternalFrontendPlugin<
TRoutes extends AnyRoutes = AnyRoutes,
TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes,
> extends BackstagePlugin<TRoutes, TExternalRoutes> {
> extends FrontendPlugin<TRoutes, TExternalRoutes> {
readonly version: 'v1';
readonly extensions: Extension<unknown>[];
readonly featureFlags: FeatureFlagConfig[];
@@ -63,7 +83,7 @@ export function createFrontendPlugin<
TExtensions extends readonly ExtensionDefinition[] = [],
>(
options: PluginOptions<TId, TRoutes, TExternalRoutes, TExtensions>,
): BackstagePlugin<
): FrontendPlugin<
TRoutes,
TExternalRoutes,
{
@@ -105,7 +125,7 @@ export function createFrontendPlugin<
}
return {
$$type: '@backstage/BackstagePlugin',
$$type: '@backstage/FrontendPlugin',
version: 'v1',
id: options.id,
routes: options.routes ?? ({} as TRoutes),
@@ -135,15 +155,33 @@ export function createFrontendPlugin<
extensions: [...nonOverriddenExtensions, ...overrides.extensions],
});
},
} as InternalBackstagePlugin<TRoutes, TExternalRoutes>;
} as InternalFrontendPlugin<TRoutes, TExternalRoutes>;
}
/** @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'
) {
// Make sure we throw if invalid
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') {
@@ -39,8 +39,15 @@ export {
export {
createPlugin,
createFrontendPlugin,
type FrontendPlugin,
type BackstagePlugin,
type PluginOptions,
} from './createFrontendPlugin';
export {
createFrontendModule,
type FrontendModule,
type CreateFrontendModuleOptions,
} 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,
@@ -16,6 +16,7 @@
import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing';
import { ExtensionDefinition } from './createExtension';
import { FrontendPlugin } from './createFrontendPlugin';
/**
* Feature flag configuration.
@@ -40,26 +41,13 @@ export type ExtensionMap<
get<TId extends keyof TExtensionMap>(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<TId extends keyof TExtensionMap>(id: TId): TExtensionMap[TId];
withOverrides(options: {
extensions: Array<ExtensionDefinition>;
}): BackstagePlugin<TRoutes, TExternalRoutes, TExtensionMap>;
}
/** @public */
export interface ExtensionOverrides {
readonly $$type: '@backstage/ExtensionOverrides';
}
/** @public */
export type FrontendFeature = BackstagePlugin | ExtensionOverrides;
/**
* @public
* @deprecated import from {@link @backstage/frontend-app-api#FrontendFeature} instead
*/
export type FrontendFeature = FrontendPlugin | ExtensionOverrides;
+1 -1
View File
@@ -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';
@@ -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';
+2 -2
View File
@@ -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<undefined>;
},
+2 -2
View File
@@ -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<
{},
{},
{
+2 -2
View File
@@ -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<
{},
{},
{
+2 -2
View File
@@ -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<undefined>;
},
+2 -2
View File
@@ -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<undefined>;
},
+2 -2
View File
@@ -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<undefined>;
catalogEntity: RouteRef<{
+2 -2
View File
@@ -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<undefined>;
},
+2 -2
View File
@@ -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<
{},
{},
{
+2 -2
View File
@@ -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<undefined>;
},
+2 -2
View File
@@ -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<undefined>;
+2 -2
View File
@@ -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<undefined>;
selectedTemplate: SubRouteRef<
+2 -2
View File
@@ -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<undefined>;
},
+2 -2
View File
@@ -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<undefined>;
docRoot: RouteRef<{
+2 -2
View File
@@ -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<undefined>;
},