Move ExtensionFactoryMiddleware from frontend-plugin-api to frontend-app-api

The `ExtensionFactoryMiddleware` type is only used by the app-level wiring
and has been moved to `@backstage/frontend-app-api` as its canonical location.

The type is preserved in `@backstage/frontend-plugin-api` with a `@deprecated`
tag pointing to the new location. All internal usages have been updated to
import from the new location.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-05 10:37:51 +01:00
parent d0b53e39fd
commit dab6c46168
12 changed files with 123 additions and 63 deletions
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': patch
---
Added the `ExtensionFactoryMiddleware` type as a public export.
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
Deprecated the `ExtensionFactoryMiddleware` type, which has been moved to `@backstage/frontend-app-api`.
+16 -1
View File
@@ -4,10 +4,13 @@
```ts
import { ApiHolder } from '@backstage/core-plugin-api';
import { ApiHolder as ApiHolder_2 } from '@backstage/frontend-plugin-api';
import { AppNode } from '@backstage/frontend-plugin-api';
import { AppTree } from '@backstage/frontend-plugin-api';
import { ConfigApi } from '@backstage/core-plugin-api';
import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api';
import { ExtensionDataContainer } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionDataValue } from '@backstage/frontend-plugin-api';
import { ExternalRouteRef } from '@backstage/frontend-plugin-api';
import { FrontendFeature } from '@backstage/frontend-plugin-api';
import { FrontendPlugin } from '@backstage/frontend-plugin-api';
@@ -177,6 +180,18 @@ export type CreateSpecializedAppOptions = {
};
};
// @public (undocumented)
export type ExtensionFactoryMiddleware = (
originalFactory: (contextOverrides?: {
config?: JsonObject;
}) => ExtensionDataContainer<ExtensionDataRef>,
context: {
node: AppNode;
apis: ApiHolder_2;
config?: JsonObject;
},
) => Iterable<ExtensionDataValue<any, any>>;
// @public
export type FrontendPluginInfoResolver = (ctx: {
packageJson(): Promise<JsonObject | undefined>;
@@ -19,7 +19,6 @@ import {
Extension,
ExtensionDataRef,
ExtensionDefinition,
ExtensionFactoryMiddleware,
ExtensionInput,
PortableSchema,
ResolvedExtensionInput,
@@ -29,6 +28,7 @@ import {
createExtensionInput,
createFrontendPlugin,
} from '@backstage/frontend-plugin-api';
import { ExtensionFactoryMiddleware } from '../wiring/types';
import {
createAppNodeInstance,
instantiateAppNodeTree,
@@ -18,10 +18,10 @@ import {
ApiHolder,
ExtensionDataContainer,
ExtensionDataRef,
ExtensionFactoryMiddleware,
ExtensionInput,
ResolvedExtensionInputs,
} from '@backstage/frontend-plugin-api';
import { ExtensionFactoryMiddleware } from '../wiring/types';
import mapValues from 'lodash/mapValues';
import { AppNode, AppNodeInstance } from '@backstage/frontend-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
@@ -29,9 +29,9 @@ import {
createApiFactory,
routeResolutionApiRef,
AppNode,
ExtensionFactoryMiddleware,
FrontendFeature,
} from '@backstage/frontend-plugin-api';
import { ExtensionFactoryMiddleware } from './types';
import {
AnyApiFactory,
ApiHolder,
@@ -20,3 +20,4 @@ export {
} from './createSpecializedApp';
export { type FrontendPluginInfoResolver } from './createPluginInfoAttacher';
export { type AppError, type AppErrorTypes } from './createErrorCollector';
export { type ExtensionFactoryMiddleware } from './types';
@@ -0,0 +1,36 @@
/*
* Copyright 2025 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 { JsonObject } from '@backstage/types';
import {
ApiHolder,
AppNode,
ExtensionDataContainer,
ExtensionDataRef,
ExtensionDataValue,
} from '@backstage/frontend-plugin-api';
/** @public */
export type ExtensionFactoryMiddleware = (
originalFactory: (contextOverrides?: {
config?: JsonObject;
}) => ExtensionDataContainer<ExtensionDataRef>,
context: {
node: AppNode;
apis: ApiHolder;
config?: JsonObject;
},
) => Iterable<ExtensionDataValue<any, any>>;
+1 -1
View File
@@ -8,7 +8,7 @@ import { AppErrorTypes } from '@backstage/frontend-app-api';
import { Config } from '@backstage/config';
import { ConfigApi } from '@backstage/frontend-plugin-api';
import { CreateAppRouteBinder } from '@backstage/frontend-app-api';
import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api';
import { ExtensionFactoryMiddleware } from '@backstage/frontend-app-api';
import { FrontendFeature } from '@backstage/frontend-plugin-api';
import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api';
import { FrontendPluginInfoResolver } from '@backstage/frontend-app-api';
+1 -1
View File
@@ -18,7 +18,6 @@ import { JSX, lazy, ReactNode, Suspense } from 'react';
import {
ConfigApi,
coreExtensionData,
ExtensionFactoryMiddleware,
FrontendFeature,
FrontendFeatureLoader,
} from '@backstage/frontend-plugin-api';
@@ -31,6 +30,7 @@ import { ConfigReader } from '@backstage/config';
import {
CreateAppRouteBinder,
createSpecializedApp,
ExtensionFactoryMiddleware,
FrontendPluginInfoResolver,
} from '@backstage/frontend-app-api';
import appPlugin from '@backstage/plugin-app';
+51 -56
View File
@@ -3,17 +3,13 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AnyRouteRefParams as AnyRouteRefParams_2 } from '@backstage/frontend-plugin-api';
import { ApiRef as ApiRef_2 } from '@backstage/frontend-plugin-api';
import { ApiRef as ApiRef_2 } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import type { Config } from '@backstage/config';
import { ConfigurableExtensionDataRef as ConfigurableExtensionDataRef_2 } from '@backstage/frontend-plugin-api';
import { Expand } from '@backstage/types';
import { ExpandRecursive } from '@backstage/types';
import { ExtensionBlueprint as ExtensionBlueprint_2 } from '@backstage/frontend-plugin-api';
import { ExtensionBlueprintParams as ExtensionBlueprintParams_2 } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef as ExtensionDataRef_2 } from '@backstage/frontend-plugin-api';
import { ExtensionInput as ExtensionInput_2 } from '@backstage/frontend-plugin-api';
import { JsonObject } from '@backstage/types';
import { JsonValue } from '@backstage/types';
import { JSX as JSX_2 } from 'react';
@@ -21,7 +17,6 @@ import { JSX as JSX_3 } from 'react/jsx-runtime';
import { Observable } from '@backstage/types';
import { PropsWithChildren } from 'react';
import { ReactNode } from 'react';
import { SwappableComponentRef as SwappableComponentRef_2 } from '@backstage/frontend-plugin-api';
import type { z } from 'zod';
// @public
@@ -82,12 +77,12 @@ export type AnalyticsImplementation = {
};
// @public
export const AnalyticsImplementationBlueprint: ExtensionBlueprint_2<{
export const AnalyticsImplementationBlueprint: ExtensionBlueprint<{
kind: 'analytics';
params: <TDeps extends { [name in string]: unknown }>(
params: AnalyticsImplementationFactory<TDeps>,
) => ExtensionBlueprintParams_2<AnalyticsImplementationFactory<{}>>;
output: ExtensionDataRef_2<
) => ExtensionBlueprintParams<AnalyticsImplementationFactory<{}>>;
output: ExtensionDataRef<
AnalyticsImplementationFactory<{}>,
'core.analytics.factory',
{}
@@ -96,7 +91,7 @@ export const AnalyticsImplementationBlueprint: ExtensionBlueprint_2<{
config: {};
configInput: {};
dataRefs: {
factory: ConfigurableExtensionDataRef_2<
factory: ConfigurableExtensionDataRef<
AnalyticsImplementationFactory<{}>,
'core.analytics.factory',
{}
@@ -149,7 +144,7 @@ export type AnyRouteRefParams =
| undefined;
// @public
export const ApiBlueprint: ExtensionBlueprint_2<{
export const ApiBlueprint: ExtensionBlueprint<{
kind: 'api';
params: <
TApi,
@@ -157,13 +152,13 @@ export const ApiBlueprint: ExtensionBlueprint_2<{
TDeps extends { [name in string]: unknown },
>(
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams_2<AnyApiFactory>;
output: ExtensionDataRef_2<AnyApiFactory, 'core.api.factory', {}>;
) => ExtensionBlueprintParams<AnyApiFactory>;
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
inputs: {};
config: {};
configInput: {};
dataRefs: {
factory: ConfigurableExtensionDataRef_2<
factory: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
@@ -388,16 +383,16 @@ export interface ConfigurableExtensionDataRef<
// @public (undocumented)
export const coreExtensionData: {
title: ConfigurableExtensionDataRef_2<string, 'core.title', {}>;
icon: ConfigurableExtensionDataRef_2<IconElement, 'core.icon', {}>;
reactElement: ConfigurableExtensionDataRef_2<
title: ConfigurableExtensionDataRef<string, 'core.title', {}>;
icon: ConfigurableExtensionDataRef<IconElement, 'core.icon', {}>;
reactElement: ConfigurableExtensionDataRef<
JSX_2.Element,
'core.reactElement',
{}
>;
routePath: ConfigurableExtensionDataRef_2<string, 'core.routing.path', {}>;
routeRef: ConfigurableExtensionDataRef_2<
RouteRef<AnyRouteRefParams_2>,
routePath: ConfigurableExtensionDataRef<string, 'core.routing.path', {}>;
routeRef: ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{}
>;
@@ -909,7 +904,7 @@ export const errorApiRef: ApiRef<ErrorApi>;
// @public (undocumented)
export const ErrorDisplay: {
(props: ErrorDisplayProps): JSX.Element | null;
ref: SwappableComponentRef_2<ErrorDisplayProps, ErrorDisplayProps>;
ref: SwappableComponentRef<ErrorDisplayProps, ErrorDisplayProps>;
};
// @public (undocumented)
@@ -956,7 +951,7 @@ export interface ExtensionBlueprint<
// (undocumented)
make<
TName extends string | undefined,
TParamsInput extends AnyParamsInput_2<NonNullable<T['params']>>,
TParamsInput extends AnyParamsInput<NonNullable<T['params']>>,
UParentInputs extends ExtensionDataRef,
>(args: {
name?: TName;
@@ -1011,7 +1006,7 @@ export interface ExtensionBlueprint<
};
factory(
originalFactory: <
TParamsInput extends AnyParamsInput_2<NonNullable<T['params']>>,
TParamsInput extends AnyParamsInput<NonNullable<T['params']>>,
>(
params: TParamsInput extends ExtensionBlueprintDefineParams
? TParamsInput
@@ -1226,7 +1221,7 @@ export type ExtensionDefinitionParameters = {
params?: object | ExtensionBlueprintDefineParams;
};
// @public (undocumented)
// @public @deprecated (undocumented)
export type ExtensionFactoryMiddleware = (
originalFactory: (contextOverrides?: {
config?: JsonObject;
@@ -1471,14 +1466,14 @@ export const microsoftAuthApiRef: ApiRef<
>;
// @public
export const NavItemBlueprint: ExtensionBlueprint_2<{
export const NavItemBlueprint: ExtensionBlueprint<{
kind: 'nav-item';
params: {
title: string;
icon: IconComponent;
routeRef: RouteRef<undefined>;
};
output: ExtensionDataRef_2<
output: ExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -1491,7 +1486,7 @@ export const NavItemBlueprint: ExtensionBlueprint_2<{
config: {};
configInput: {};
dataRefs: {
target: ConfigurableExtensionDataRef_2<
target: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent;
@@ -1506,7 +1501,7 @@ export const NavItemBlueprint: ExtensionBlueprint_2<{
// @public (undocumented)
export const NotFoundErrorPage: {
(props: NotFoundErrorPageProps): JSX.Element | null;
ref: SwappableComponentRef_2<NotFoundErrorPageProps, NotFoundErrorPageProps>;
ref: SwappableComponentRef<NotFoundErrorPageProps, NotFoundErrorPageProps>;
};
// @public (undocumented)
@@ -1594,7 +1589,7 @@ export interface OverridableExtensionDefinition<
TExtraInputs extends {
[inputName in string]: ExtensionInput;
},
TParamsInput extends AnyParamsInput<NonNullable<T['params']>>,
TParamsInput extends AnyParamsInput_2<NonNullable<T['params']>>,
UParentInputs extends ExtensionDataRef,
>(
args: Expand<
@@ -1620,7 +1615,7 @@ export interface OverridableExtensionDefinition<
};
factory?(
originalFactory: <
TFactoryParamsReturn extends AnyParamsInput<
TFactoryParamsReturn extends AnyParamsInput_2<
NonNullable<T['params']>
>,
>(
@@ -1719,7 +1714,7 @@ export interface OverridableFrontendPlugin<
}
// @public
export const PageBlueprint: ExtensionBlueprint_2<{
export const PageBlueprint: ExtensionBlueprint<{
kind: 'page';
params: {
defaultPath?: [Error: `Use the 'path' param instead`];
@@ -1731,23 +1726,23 @@ export const PageBlueprint: ExtensionBlueprint_2<{
noHeader?: boolean;
};
output:
| ExtensionDataRef_2<string, 'core.routing.path', {}>
| ExtensionDataRef_2<
RouteRef<AnyRouteRefParams_2>,
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ExtensionDataRef_2<JSX_2.Element, 'core.reactElement', {}>
| ExtensionDataRef_2<
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ExtensionDataRef<
string,
'core.title',
{
optional: true;
}
>
| ExtensionDataRef_2<
| ExtensionDataRef<
IconElement,
'core.icon',
{
@@ -1755,24 +1750,24 @@ export const PageBlueprint: ExtensionBlueprint_2<{
}
>;
inputs: {
pages: ExtensionInput_2<
| ConfigurableExtensionDataRef_2<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef_2<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef_2<
RouteRef<AnyRouteRefParams_2>,
pages: ExtensionInput<
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ConfigurableExtensionDataRef_2<
| ConfigurableExtensionDataRef<
string,
'core.title',
{
optional: true;
}
>
| ConfigurableExtensionDataRef_2<
| ConfigurableExtensionDataRef<
IconElement,
'core.icon',
{
@@ -1800,7 +1795,7 @@ export const PageBlueprint: ExtensionBlueprint_2<{
// @public
export const PageLayout: {
(props: PageLayoutProps): JSX.Element | null;
ref: SwappableComponentRef_2<PageLayoutProps, PageLayoutProps>;
ref: SwappableComponentRef<PageLayoutProps, PageLayoutProps>;
};
// @public
@@ -1839,14 +1834,14 @@ export type PendingOAuthRequest = {
};
// @public
export const PluginHeaderActionBlueprint: ExtensionBlueprint_2<{
export const PluginHeaderActionBlueprint: ExtensionBlueprint<{
kind: 'plugin-header-action';
params: (params: {
loader: () => Promise<JSX.Element>;
}) => ExtensionBlueprintParams_2<{
}) => ExtensionBlueprintParams<{
loader: () => Promise<JSX.Element>;
}>;
output: ExtensionDataRef_2<JSX_2, 'core.reactElement', {}>;
output: ExtensionDataRef<JSX_2, 'core.reactElement', {}>;
inputs: {};
config: {};
configInput: {};
@@ -1909,7 +1904,7 @@ export type ProfileInfoApi = {
// @public (undocumented)
export const Progress: {
(props: ProgressProps): JSX.Element | null;
ref: SwappableComponentRef_2<ProgressProps, ProgressProps>;
ref: SwappableComponentRef<ProgressProps, ProgressProps>;
};
// @public (undocumented)
@@ -2020,7 +2015,7 @@ export type StorageValueSnapshot<TValue extends JsonValue> =
};
// @public
export const SubPageBlueprint: ExtensionBlueprint_2<{
export const SubPageBlueprint: ExtensionBlueprint<{
kind: 'sub-page';
params: {
path: string;
@@ -2030,17 +2025,17 @@ export const SubPageBlueprint: ExtensionBlueprint_2<{
routeRef?: RouteRef;
};
output:
| ExtensionDataRef_2<string, 'core.routing.path', {}>
| ExtensionDataRef_2<
RouteRef<AnyRouteRefParams_2>,
| ExtensionDataRef<string, 'core.routing.path', {}>
| ExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
{
optional: true;
}
>
| ExtensionDataRef_2<JSX_2, 'core.reactElement', {}>
| ExtensionDataRef_2<string, 'core.title', {}>
| ExtensionDataRef_2<
| ExtensionDataRef<JSX_2, 'core.reactElement', {}>
| ExtensionDataRef<string, 'core.title', {}>
| ExtensionDataRef<
IconElement,
'core.icon',
{
@@ -60,7 +60,10 @@ export type ExtensionDataContainer<UExtensionData extends ExtensionDataRef> =
: never;
};
/** @public */
/**
* @public
* @deprecated Moved to {@link @backstage/frontend-app-api#ExtensionFactoryMiddleware}
*/
export type ExtensionFactoryMiddleware = (
originalFactory: (contextOverrides?: {
config?: JsonObject;