diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx
index 7d62103727..e2c7581e63 100644
--- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx
+++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx
@@ -40,14 +40,8 @@ const baseOverrides = createExtensionOverrides({
namespace: 'app',
name: 'root',
attachTo: { id: 'app', input: 'root' },
- output: {
- element: coreExtensionData.reactElement,
- },
- factory() {
- return {
- element:
root
,
- };
- },
+ output: [coreExtensionData.reactElement],
+ factory: () => [coreExtensionData.reactElement(root
)],
}),
],
});
diff --git a/packages/frontend-app-api/src/extensions/App.tsx b/packages/frontend-app-api/src/extensions/App.tsx
index 7794aceb79..ba6c5f65f1 100644
--- a/packages/frontend-app-api/src/extensions/App.tsx
+++ b/packages/frontend-app-api/src/extensions/App.tsx
@@ -18,51 +18,38 @@ import React from 'react';
import {
ExtensionBoundary,
coreExtensionData,
- createApiExtension,
createComponentExtension,
createExtension,
createExtensionInput,
- createThemeExtension,
- createTranslationExtension,
IconBundleBlueprint,
+ ThemeBlueprint,
+ ApiBlueprint,
+ TranslationBlueprint,
} from '@backstage/frontend-plugin-api';
export const App = createExtension({
namespace: 'app',
attachTo: { id: 'root', input: 'default' }, // ignored
inputs: {
- apis: createExtensionInput({
- api: createApiExtension.factoryDataRef,
+ apis: createExtensionInput([ApiBlueprint.dataRefs.factory]),
+ themes: createExtensionInput([ThemeBlueprint.dataRefs.theme]),
+ components: createExtensionInput([
+ createComponentExtension.componentDataRef,
+ ]),
+ translations: createExtensionInput([
+ TranslationBlueprint.dataRefs.translation,
+ ]),
+ icons: createExtensionInput([IconBundleBlueprint.dataRefs.icons]),
+ root: createExtensionInput([coreExtensionData.reactElement], {
+ singleton: true,
}),
- themes: createExtensionInput({
- theme: createThemeExtension.themeDataRef,
- }),
- components: createExtensionInput({
- component: createComponentExtension.componentDataRef,
- }),
- translations: createExtensionInput({
- translation: createTranslationExtension.translationDataRef,
- }),
- icons: createExtensionInput({
- icon: IconBundleBlueprint.dataRefs.icons,
- }),
- root: createExtensionInput(
- {
- element: coreExtensionData.reactElement,
- },
- { singleton: true },
+ },
+ output: [coreExtensionData.reactElement],
+ factory: ({ node, inputs }) => [
+ coreExtensionData.reactElement(
+
+ {inputs.root.get(coreExtensionData.reactElement)}
+ ,
),
- },
- output: {
- root: coreExtensionData.reactElement,
- },
- factory({ node, inputs }) {
- return {
- root: (
-
- {inputs.root.output.element}
-
- ),
- };
- },
+ ],
});
diff --git a/packages/frontend-app-api/src/extensions/AppLayout.tsx b/packages/frontend-app-api/src/extensions/AppLayout.tsx
index 7be0029c25..9fadba8a93 100644
--- a/packages/frontend-app-api/src/extensions/AppLayout.tsx
+++ b/packages/frontend-app-api/src/extensions/AppLayout.tsx
@@ -27,30 +27,20 @@ export const AppLayout = createExtension({
name: 'layout',
attachTo: { id: 'app/root', input: 'children' },
inputs: {
- nav: createExtensionInput(
- {
- element: coreExtensionData.reactElement,
- },
- { singleton: true },
+ nav: createExtensionInput([coreExtensionData.reactElement], {
+ singleton: true,
+ }),
+ content: createExtensionInput([coreExtensionData.reactElement], {
+ singleton: true,
+ }),
+ },
+ output: [coreExtensionData.reactElement],
+ factory: ({ inputs }) => [
+ coreExtensionData.reactElement(
+
+ {inputs.nav.get(coreExtensionData.reactElement)}
+ {inputs.content.get(coreExtensionData.reactElement)}
+ ,
),
- content: createExtensionInput(
- {
- element: coreExtensionData.reactElement,
- },
- { singleton: true },
- ),
- },
- output: {
- element: coreExtensionData.reactElement,
- },
- factory({ inputs }) {
- return {
- element: (
-
- {inputs.nav.output.element}
- {inputs.content.output.element}
-
- ),
- };
- },
+ ],
});
diff --git a/packages/frontend-app-api/src/extensions/AppNav.tsx b/packages/frontend-app-api/src/extensions/AppNav.tsx
index ffef1c7c61..49c95e84f2 100644
--- a/packages/frontend-app-api/src/extensions/AppNav.tsx
+++ b/packages/frontend-app-api/src/extensions/AppNav.tsx
@@ -86,33 +86,27 @@ export const AppNav = createExtension({
name: 'nav',
attachTo: { id: 'app/layout', input: 'nav' },
inputs: {
- items: createExtensionInput({
- target: createNavItemExtension.targetDataRef,
+ items: createExtensionInput([createNavItemExtension.targetDataRef]),
+ logos: createExtensionInput([createNavLogoExtension.logoElementsDataRef], {
+ singleton: true,
+ optional: true,
}),
- logos: createExtensionInput(
- {
- elements: createNavLogoExtension.logoElementsDataRef,
- },
- {
- singleton: true,
- optional: true,
- },
+ },
+ output: [coreExtensionData.reactElement],
+ factory: ({ inputs }) => [
+ coreExtensionData.reactElement(
+
+
+
+ {inputs.items.map((item, index) => (
+
+ ))}
+ ,
),
- },
- output: {
- element: coreExtensionData.reactElement,
- },
- factory({ inputs }) {
- return {
- element: (
-
-
-
- {inputs.items.map((item, index) => (
-
- ))}
-
- ),
- };
- },
+ ],
});
diff --git a/packages/frontend-app-api/src/extensions/AppRoot.tsx b/packages/frontend-app-api/src/extensions/AppRoot.tsx
index f3a962dd10..a158386d04 100644
--- a/packages/frontend-app-api/src/extensions/AppRoot.tsx
+++ b/packages/frontend-app-api/src/extensions/AppRoot.tsx
@@ -23,12 +23,12 @@ import React, {
useState,
} from 'react';
import {
+ AppRootWrapperBlueprint,
+ RouterBlueprint,
+ SignInPageBlueprint,
coreExtensionData,
- createAppRootWrapperExtension,
createExtension,
createExtensionInput,
- createRouterExtension,
- createSignInPageExtension,
} from '@backstage/frontend-plugin-api';
import {
IdentityApi,
@@ -48,52 +48,54 @@ export const AppRoot = createExtension({
name: 'root',
attachTo: { id: 'app', input: 'root' },
inputs: {
- router: createExtensionInput(
- { component: createRouterExtension.componentDataRef },
- { singleton: true, optional: true },
- ),
- signInPage: createExtensionInput(
- { component: createSignInPageExtension.componentDataRef },
- { singleton: true, optional: true },
- ),
- children: createExtensionInput(
- { element: coreExtensionData.reactElement },
- { singleton: true },
- ),
- elements: createExtensionInput({
- element: coreExtensionData.reactElement,
+ router: createExtensionInput([RouterBlueprint.dataRefs.component], {
+ singleton: true,
+ optional: true,
}),
- wrappers: createExtensionInput({
- component: createAppRootWrapperExtension.componentDataRef,
+ signInPage: createExtensionInput([SignInPageBlueprint.dataRefs.component], {
+ singleton: true,
+ optional: true,
}),
+ children: createExtensionInput([coreExtensionData.reactElement], {
+ singleton: true,
+ }),
+ elements: createExtensionInput([coreExtensionData.reactElement]),
+ wrappers: createExtensionInput([
+ AppRootWrapperBlueprint.dataRefs.component,
+ ]),
},
- output: {
- element: coreExtensionData.reactElement,
- },
+ output: [coreExtensionData.reactElement],
factory({ inputs }) {
let content: React.ReactNode = (
<>
{inputs.elements.map(el => (
- {el.output.element}
+
+ {el.get(coreExtensionData.reactElement)}
+
))}
- {inputs.children.output.element}
+ {inputs.children.get(coreExtensionData.reactElement)}
>
);
for (const wrapper of inputs.wrappers) {
- content = {content};
+ const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component);
+ content = {content};
}
- return {
- element: (
+ return [
+ coreExtensionData.reactElement(
{content}
-
+ ,
),
- };
+ ];
},
});
diff --git a/packages/frontend-app-api/src/extensions/AppRoutes.tsx b/packages/frontend-app-api/src/extensions/AppRoutes.tsx
index 38a9291755..6ef7e2a271 100644
--- a/packages/frontend-app-api/src/extensions/AppRoutes.tsx
+++ b/packages/frontend-app-api/src/extensions/AppRoutes.tsx
@@ -29,15 +29,13 @@ export const AppRoutes = createExtension({
name: 'routes',
attachTo: { id: 'app/layout', input: 'content' },
inputs: {
- routes: createExtensionInput({
- path: coreExtensionData.routePath,
- ref: coreExtensionData.routeRef.optional(),
- element: coreExtensionData.reactElement,
- }),
- },
- output: {
- element: coreExtensionData.reactElement,
+ routes: createExtensionInput([
+ coreExtensionData.routePath,
+ coreExtensionData.routeRef.optional(),
+ coreExtensionData.reactElement,
+ ]),
},
+ output: [coreExtensionData.reactElement],
factory({ inputs }) {
const Routes = () => {
const NotFoundErrorPage = useComponentRef(
@@ -46,8 +44,8 @@ export const AppRoutes = createExtension({
const element = useRoutes([
...inputs.routes.map(route => ({
- path: `${route.output.path}/*`,
- element: route.output.element,
+ path: `${route.get(coreExtensionData.routePath)}/*`,
+ element: route.get(coreExtensionData.reactElement),
})),
{
path: '*',
@@ -58,8 +56,6 @@ export const AppRoutes = createExtension({
return element;
};
- return {
- element: ,
- };
+ return [coreExtensionData.reactElement()];
},
});
diff --git a/packages/frontend-app-api/src/extensions/elements.tsx b/packages/frontend-app-api/src/extensions/elements.tsx
index 0a764583bb..1ab7f97ba2 100644
--- a/packages/frontend-app-api/src/extensions/elements.tsx
+++ b/packages/frontend-app-api/src/extensions/elements.tsx
@@ -15,31 +15,36 @@
*/
import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
-import {
- createAppRootElementExtension,
- createSchemaFromZod,
-} from '@backstage/frontend-plugin-api';
+import { AppRootElementBlueprint } from '@backstage/frontend-plugin-api';
import React from 'react';
-export const oauthRequestDialogAppRootElement = createAppRootElementExtension({
+export const oauthRequestDialogAppRootElement = AppRootElementBlueprint.make({
namespace: 'app',
name: 'oauth-request-dialog',
- element: ,
+ params: {
+ element: ,
+ },
});
-export const alertDisplayAppRootElement = createAppRootElementExtension({
- namespace: 'app',
- name: 'alert-display',
- configSchema: createSchemaFromZod(z =>
- z.object({
- transientTimeoutMs: z.number().default(5000),
- anchorOrigin: z
- .object({
- vertical: z.enum(['top', 'bottom']).default('top'),
- horizontal: z.enum(['left', 'center', 'right']).default('center'),
- })
- .default({}),
- }),
- ),
- element: ({ config }) => ,
-});
+export const alertDisplayAppRootElement =
+ AppRootElementBlueprint.makeWithOverrides({
+ namespace: 'app',
+ name: 'alert-display',
+ config: {
+ schema: {
+ transientTimeoutMs: z => z.number().default(5000),
+ anchorOrigin: z =>
+ z
+ .object({
+ vertical: z.enum(['top', 'bottom']).default('top'),
+ horizontal: z.enum(['left', 'center', 'right']).default('center'),
+ })
+ .default({}),
+ },
+ },
+ factory: (originalFactory, { config }) => {
+ return originalFactory({
+ element: () => ,
+ });
+ },
+ });
diff --git a/packages/frontend-app-api/src/extensions/themes.tsx b/packages/frontend-app-api/src/extensions/themes.tsx
index ea11b6a638..a4fd0b253e 100644
--- a/packages/frontend-app-api/src/extensions/themes.tsx
+++ b/packages/frontend-app-api/src/extensions/themes.tsx
@@ -21,24 +21,34 @@ import {
} from '@backstage/theme';
import DarkIcon from '@material-ui/icons/Brightness2';
import LightIcon from '@material-ui/icons/WbSunny';
-import { createThemeExtension } from '@backstage/frontend-plugin-api';
+import { ThemeBlueprint } from '@backstage/frontend-plugin-api';
-export const LightTheme = createThemeExtension({
- id: 'light',
- title: 'Light Theme',
- variant: 'light',
- icon: ,
- Provider: ({ children }) => (
-
- ),
+export const LightTheme = ThemeBlueprint.make({
+ name: 'light',
+ params: {
+ theme: {
+ id: 'light',
+ title: 'Light Theme',
+ variant: 'light',
+ icon: ,
+ Provider: ({ children }) => (
+
+ ),
+ },
+ },
});
-export const DarkTheme = createThemeExtension({
- id: 'dark',
- title: 'Dark Theme',
- variant: 'dark',
- icon: ,
- Provider: ({ children }) => (
-
- ),
+export const DarkTheme = ThemeBlueprint.make({
+ name: 'dark',
+ params: {
+ theme: {
+ id: 'dark',
+ title: 'Dark Theme',
+ variant: 'dark',
+ icon: ,
+ Provider: ({ children }) => (
+
+ ),
+ },
+ },
});
diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts
index c22cb6ff1d..8a54f3e73e 100644
--- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts
+++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts
@@ -50,22 +50,23 @@ function createTestExtension(options: {
attachTo: options.parent
? { id: `test/${options.parent}`, input: 'children' }
: { id: 'app/routes', input: 'routes' },
- output: {
- element: coreExtensionData.reactElement,
- path: coreExtensionData.routePath.optional(),
- routeRef: coreExtensionData.routeRef.optional(),
- },
+ output: [
+ coreExtensionData.reactElement,
+ coreExtensionData.routePath.optional(),
+ coreExtensionData.routeRef.optional(),
+ ],
inputs: {
- children: createExtensionInput({
- element: coreExtensionData.reactElement,
- }),
+ children: createExtensionInput([coreExtensionData.reactElement]),
},
- factory() {
- return {
- path: options.path,
- routeRef: options.routeRef,
- element: React.createElement('div'),
- };
+ *factory() {
+ if (options.path) {
+ yield coreExtensionData.routePath(options.path);
+ }
+ if (options.routeRef) {
+ yield coreExtensionData.routeRef(options.routeRef);
+ }
+
+ yield coreExtensionData.reactElement(React.createElement('div'));
},
});
}
diff --git a/packages/frontend-app-api/src/tree/createAppTree.test.ts b/packages/frontend-app-api/src/tree/createAppTree.test.ts
index f24f199d5d..f5df5270cd 100644
--- a/packages/frontend-app-api/src/tree/createAppTree.test.ts
+++ b/packages/frontend-app-api/src/tree/createAppTree.test.ts
@@ -25,8 +25,8 @@ import { createAppTree } from './createAppTree';
const extBase = {
id: 'test',
attachTo: { id: 'app', input: 'root' },
- output: {},
- factory: () => ({}),
+ output: [],
+ factory: () => [],
};
describe('createAppTree', () => {
@@ -60,8 +60,8 @@ describe('createAppTree', () => {
name: 'app',
attachTo: { id: 'app/routes', input: 'route' },
inputs: {},
- output: {},
- factory: () => ({}),
+ output: [],
+ factory: () => [],
}),
],
}),
diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts
index d9b65555fe..740f05d5f7 100644
--- a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts
+++ b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts
@@ -23,8 +23,8 @@ const extension = resolveExtensionDefinition(
createExtension({
name: 'test',
attachTo: { id: 'nonexistent', input: 'nonexistent' },
- output: {},
- factory: () => ({}),
+ output: [],
+ factory: () => [],
}),
) as Extension;
diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx
index ead9422a13..c79d8eed76 100644
--- a/packages/frontend-app-api/src/wiring/createApp.test.tsx
+++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx
@@ -20,9 +20,9 @@ import {
coreExtensionData,
createExtension,
createExtensionOverrides,
- createPageExtension,
+ PageBlueprint,
createFrontendPlugin,
- createThemeExtension,
+ ThemeBlueprint,
} from '@backstage/frontend-plugin-api';
import { screen, waitFor } from '@testing-library/react';
import { CreateAppFeatureLoader, createApp } from './createApp';
@@ -47,11 +47,16 @@ describe('createApp', () => {
createFrontendPlugin({
id: 'test',
extensions: [
- createThemeExtension({
- id: 'derp',
- title: 'Derp',
- variant: 'dark',
- Provider: () => Derp
,
+ ThemeBlueprint.make({
+ name: 'derp',
+ params: {
+ theme: {
+ id: 'derp',
+ title: 'Derp',
+ variant: 'dark',
+ Provider: () => Derp
,
+ },
+ },
}),
],
}),
@@ -71,18 +76,22 @@ describe('createApp', () => {
createFrontendPlugin({
id: duplicatedFeatureId,
extensions: [
- createPageExtension({
- defaultPath: '/',
- loader: async () => First Page
,
+ PageBlueprint.make({
+ params: {
+ defaultPath: '/',
+ loader: async () => First Page
,
+ },
}),
],
}),
createFrontendPlugin({
id: duplicatedFeatureId,
extensions: [
- createPageExtension({
- defaultPath: '/',
- loader: async () => Last Page
,
+ PageBlueprint.make({
+ params: {
+ defaultPath: '/',
+ loader: async () => Last Page
,
+ },
}),
],
}),
@@ -110,9 +119,11 @@ describe('createApp', () => {
createFrontendPlugin({
id: 'test',
extensions: [
- createPageExtension({
- defaultPath: '/',
- loader: async () => {config.getString('key')}
,
+ PageBlueprint.make({
+ params: {
+ defaultPath: '/',
+ loader: async () => {config.getString('key')}
,
+ },
}),
],
}),
@@ -170,7 +181,7 @@ describe('createApp', () => {
createExtension({
name: 'first',
attachTo: { id: 'app', input: 'root' },
- output: { element: coreExtensionData.reactElement },
+ output: [coreExtensionData.reactElement],
factory() {
const Component = () => {
const flagsApi = useApi(featureFlagsApiRef);
@@ -184,7 +195,7 @@ describe('createApp', () => {
);
};
- return { element: };
+ return [coreExtensionData.reactElement()];
},
}),
],
@@ -197,8 +208,8 @@ describe('createApp', () => {
name: 'root',
attachTo: { id: 'app', input: 'root' },
disabled: true,
- output: {},
- factory: () => ({}),
+ output: [],
+ factory: () => [],
}),
],
}),
@@ -221,14 +232,16 @@ describe('createApp', () => {
createFrontendPlugin({
id: 'my-plugin',
extensions: [
- createPageExtension({
- defaultPath: '/',
- loader: async () => {
- const Component = () => {
- appTreeApi = useApi(appTreeApiRef);
- return My Plugin Page
;
- };
- return ;
+ PageBlueprint.make({
+ params: {
+ defaultPath: '/',
+ loader: async () => {
+ const Component = () => {
+ appTreeApi = useApi(appTreeApiRef);
+ return My Plugin Page
;
+ };
+ return ;
+ },
},
}),
],
@@ -250,7 +263,7 @@ describe('createApp', () => {
content [
routes [
-
+
]
]
diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx
index 6f23f4cf2c..ca42dce3e4 100644
--- a/packages/frontend-app-api/src/wiring/createApp.tsx
+++ b/packages/frontend-app-api/src/wiring/createApp.tsx
@@ -17,18 +17,18 @@
import React, { JSX, ReactNode } from 'react';
import { ConfigReader } from '@backstage/config';
import {
+ ApiBlueprint,
AppTree,
appTreeApiRef,
componentsApiRef,
coreExtensionData,
- createApiExtension,
- createThemeExtension,
- createTranslationExtension,
FrontendFeature,
IconBundleBlueprint,
iconsApiRef,
RouteResolutionApi,
routeResolutionApiRef,
+ ThemeBlueprint,
+ TranslationBlueprint,
} from '@backstage/frontend-plugin-api';
import { App } from '../extensions/App';
import { AppRoutes } from '../extensions/AppRoutes';
@@ -110,7 +110,9 @@ import { stringifyError } from '@backstage/errors';
import { icons as defaultIcons } from '../../../app-defaults/src/defaults';
import { getBasePath } from '../routing/getBasePath';
-const DefaultApis = defaultApis.map(factory => createApiExtension({ factory }));
+const DefaultApis = defaultApis.map(factory =>
+ ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }),
+);
export const builtinExtensions = [
App,
@@ -357,23 +359,21 @@ function createApiHolder(
const pluginApis =
tree.root.edges.attachments
.get('apis')
- ?.map(e => e.instance?.getData(createApiExtension.factoryDataRef))
+ ?.map(e => e.instance?.getData(ApiBlueprint.dataRefs.factory))
.filter((x): x is AnyApiFactory => !!x) ?? [];
const themeExtensions =
tree.root.edges.attachments
.get('themes')
- ?.map(e => e.instance?.getData(createThemeExtension.themeDataRef))
+ ?.map(e => e.instance?.getData(ThemeBlueprint.dataRefs.theme))
.filter((x): x is AppTheme => !!x) ?? [];
const translationResources =
tree.root.edges.attachments
.get('translations')
- ?.map(e =>
- e.instance?.getData(createTranslationExtension.translationDataRef),
- )
+ ?.map(e => e.instance?.getData(TranslationBlueprint.dataRefs.translation))
.filter(
- (x): x is typeof createTranslationExtension.translationDataRef.T => !!x,
+ (x): x is typeof TranslationBlueprint.dataRefs.translation.T => !!x,
) ?? [];
const extensionIcons = tree.root.edges.attachments