diff --git a/.changeset/mira-color-like.md b/.changeset/mira-color-like.md new file mode 100644 index 0000000000..fac24490be --- /dev/null +++ b/.changeset/mira-color-like.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': minor +--- + +Feature resolution logic used in `createApp` is now exposed via the `resolveFeatures` function. diff --git a/packages/frontend-defaults/report.api.md b/packages/frontend-defaults/report.api.md index 1c0ee7e6a7..1b8183e7bd 100644 --- a/packages/frontend-defaults/report.api.md +++ b/packages/frontend-defaults/report.api.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { Config } from '@backstage/config'; import { ConfigApi } from '@backstage/frontend-plugin-api'; import { CreateAppRouteBinder } from '@backstage/frontend-app-api'; import { ExtensionFactoryMiddleware } from '@backstage/frontend-app-api'; @@ -43,4 +44,10 @@ export interface CreateAppOptions { export function createPublicSignInApp(options?: CreateAppOptions): { createRoot(): React_2.JSX.Element; }; + +// @public (undocumented) +export function resolveFeatures(options: { + config: Config; + providedFeatures?: (FrontendFeature | CreateAppFeatureLoader)[]; +}): Promise; ``` diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx index a8b40a5f63..0eb4d4a8e8 100644 --- a/packages/frontend-defaults/src/createApp.tsx +++ b/packages/frontend-defaults/src/createApp.tsx @@ -16,14 +16,12 @@ import React, { JSX, ReactNode } from 'react'; import { ConfigApi, coreExtensionData } from '@backstage/frontend-plugin-api'; -import { stringifyError } from '@backstage/errors'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseUrlConfigs'; -import { getAvailableFeatures } from './discovery'; +import { resolveFeatures } from './discovery'; import { ConfigReader } from '@backstage/config'; -import appPlugin from '@backstage/plugin-app'; import { CreateAppRouteBinder, ExtensionFactoryMiddleware, @@ -89,29 +87,12 @@ export function createApp(options?: CreateAppOptions): { overrideBaseUrlConfigs(defaultConfigLoaderSync()), ); - const discoveredFeatures = getAvailableFeatures(config); - - const providedFeatures: FrontendFeature[] = []; - for (const entry of options?.features ?? []) { - if ('load' in entry) { - try { - const result = await entry.load({ config }); - providedFeatures.push(...result.features); - } catch (e) { - throw new Error( - `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError( - e, - )}`, - ); - } - } else { - providedFeatures.push(entry); - } - } - const app = createSpecializedApp({ config, - features: [appPlugin, ...discoveredFeatures, ...providedFeatures], + features: await resolveFeatures({ + config, + providedFeatures: options?.features, + }), bindRoutes: options?.bindRoutes, extensionFactoryMiddleware: options?.extensionFactoryMiddleware, }); diff --git a/packages/frontend-defaults/src/discovery.ts b/packages/frontend-defaults/src/discovery.ts index e04960dc48..b80f9a1b39 100644 --- a/packages/frontend-defaults/src/discovery.ts +++ b/packages/frontend-defaults/src/discovery.ts @@ -15,7 +15,10 @@ */ import { Config, ConfigReader } from '@backstage/config'; +import { stringifyError } from '@backstage/errors'; import { FrontendFeature } from '@backstage/frontend-app-api'; +import { CreateAppFeatureLoader } from './createApp'; +import appPlugin from '@backstage/plugin-app'; interface DiscoveryGlobal { modules: Array<{ name: string; export?: string; default: unknown }>; @@ -94,3 +97,29 @@ function isBackstageFeature(obj: unknown): obj is FrontendFeature { } return false; } + +/** @public */ +export async function resolveFeatures(options: { + config: Config; + providedFeatures?: (FrontendFeature | CreateAppFeatureLoader)[]; +}): Promise { + const discoveredFeatures = getAvailableFeatures(options.config); + const features = [appPlugin, ...discoveredFeatures]; + for (const entry of options.providedFeatures ?? []) { + if ('load' in entry) { + try { + const result = await entry.load({ config: options.config }); + features.push(...result.features); + } catch (e) { + throw new Error( + `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError( + e, + )}`, + ); + } + } else { + features.push(entry); + } + } + return features; +} diff --git a/packages/frontend-defaults/src/index.ts b/packages/frontend-defaults/src/index.ts index 7ee60a2116..ff233e00b3 100644 --- a/packages/frontend-defaults/src/index.ts +++ b/packages/frontend-defaults/src/index.ts @@ -26,3 +26,4 @@ export { type CreateAppFeatureLoader, } from './createApp'; export { createPublicSignInApp } from './createPublicSignInApp'; +export { resolveFeatures } from './discovery';