@backstage/frontend-app-api 0.15.1-next.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/frontend-internal/src/wiring/InternalExtensionDefinition.esm.js.map +1 -1
  3. package/dist/frontend-internal/src/wiring/InternalFrontendPlugin.esm.js.map +1 -1
  4. package/dist/frontend-plugin-api/src/wiring/createFrontendModule.esm.js.map +1 -1
  5. package/dist/frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js.map +1 -1
  6. package/dist/index.d.ts +140 -24
  7. package/dist/index.esm.js +1 -0
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/routing/RouteAliasResolver.esm.js +0 -2
  10. package/dist/routing/RouteAliasResolver.esm.js.map +1 -1
  11. package/dist/routing/RouteResolver.esm.js +1 -1
  12. package/dist/routing/resolveRouteBindings.esm.js +2 -0
  13. package/dist/routing/resolveRouteBindings.esm.js.map +1 -1
  14. package/dist/tree/instantiateAppNodeTree.esm.js +72 -15
  15. package/dist/tree/instantiateAppNodeTree.esm.js.map +1 -1
  16. package/dist/tree/resolveAppNodeSpecs.esm.js +50 -9
  17. package/dist/tree/resolveAppNodeSpecs.esm.js.map +1 -1
  18. package/dist/wiring/FrontendApiRegistry.esm.js +92 -0
  19. package/dist/wiring/FrontendApiRegistry.esm.js.map +1 -0
  20. package/dist/wiring/apiFactories.esm.js +185 -0
  21. package/dist/wiring/apiFactories.esm.js.map +1 -0
  22. package/dist/wiring/createErrorCollector.esm.js.map +1 -1
  23. package/dist/wiring/createPluginInfoAttacher.esm.js.map +1 -1
  24. package/dist/wiring/createSpecializedApp.esm.js +11 -283
  25. package/dist/wiring/createSpecializedApp.esm.js.map +1 -1
  26. package/dist/wiring/phaseApis.esm.js +161 -0
  27. package/dist/wiring/phaseApis.esm.js.map +1 -0
  28. package/dist/wiring/predicates.esm.js +116 -0
  29. package/dist/wiring/predicates.esm.js.map +1 -0
  30. package/dist/wiring/prepareSpecializedApp.esm.js +516 -0
  31. package/dist/wiring/prepareSpecializedApp.esm.js.map +1 -0
  32. package/dist/wiring/treeLifecycle.esm.js +186 -0
  33. package/dist/wiring/treeLifecycle.esm.js.map +1 -0
  34. package/package.json +16 -14
  35. package/dist/core-app-api/src/apis/system/ApiRegistry.esm.js +0 -50
  36. package/dist/core-app-api/src/apis/system/ApiRegistry.esm.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # @backstage/frontend-app-api
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createSpecializedApp`. This flag had no effect and was a no-op, so no behavioral changes are expected.
8
+
9
+ ### Patch Changes
10
+
11
+ - d911b72: Frontend apps now respect an explicit `pluginId` on `ApiRef`s when deciding which plugin owns an API factory.
12
+ - 0452d02: Add optional `description` field to plugin-level feature flags.
13
+ - 5fd78ba: Removed `@backstage/core-plugin-api` leakage from the public API surface. All types such as `ApiHolder` and `ConfigApi` are now imported from `@backstage/frontend-plugin-api`.
14
+ - dab6c46: Added the `ExtensionFactoryMiddleware` type as a public export.
15
+ - 3f36ce1: Clarified the `IconElement` sizing contract for the new frontend system and aligned legacy system icon rendering with the new icon API.
16
+ - 5b160f9: Added `prepareSpecializedApp` for two-phase app wiring so apps can render a bootstrap tree before full app finalization. The bootstrap phase now supports deferred `app/root.elements`, predicate-gated APIs, reusable `sessionState`, and warnings for bootstrap-visible predicates or bootstrap code that accessed APIs that only became available after finalization. Utility APIs that are materialized during bootstrap are also frozen for the lifetime of the app instance, causing deferred overrides of those APIs to be ignored and reported as app errors.
17
+ - a49a40d: Updated dependency `zod` to `^3.25.76 || ^4.0.0` & migrated to `/v3` or `/v4` imports.
18
+ - Updated dependencies
19
+ - @backstage/core-plugin-api@1.12.4
20
+ - @backstage/frontend-plugin-api@0.15.0
21
+ - @backstage/core-app-api@1.19.6
22
+ - @backstage/frontend-defaults@0.5.0
23
+ - @backstage/filter-predicates@0.1.1
24
+
25
+ ## 0.16.0-next.1
26
+
27
+ ### Minor Changes
28
+
29
+ - 92af1ae: **BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createSpecializedApp`. This flag had no effect and was a no-op, so no behavioral changes are expected.
30
+
31
+ ### Patch Changes
32
+
33
+ - 0452d02: Add optional `description` field to plugin-level feature flags.
34
+ - dab6c46: Added the `ExtensionFactoryMiddleware` type as a public export.
35
+ - Updated dependencies
36
+ - @backstage/core-app-api@1.19.6-next.1
37
+ - @backstage/frontend-plugin-api@0.15.0-next.1
38
+ - @backstage/core-plugin-api@1.12.4-next.1
39
+ - @backstage/frontend-defaults@0.5.0-next.1
40
+
3
41
  ## 0.15.1-next.0
4
42
 
5
43
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"InternalExtensionDefinition.esm.js","sources":["../../../../../frontend-internal/src/wiring/InternalExtensionDefinition.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ApiHolder,\n AppNode,\n ExtensionDefinitionAttachTo,\n ExtensionDataValue,\n ExtensionDataRef,\n OverridableExtensionDefinition,\n ExtensionDefinitionParameters,\n ExtensionInput,\n PortableSchema,\n ResolvedExtensionInputs,\n} from '@backstage/frontend-plugin-api';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueExtensionDefinition = OpaqueType.create<{\n public: OverridableExtensionDefinition<ExtensionDefinitionParameters>;\n versions:\n | {\n readonly version: 'v1';\n readonly kind?: string;\n readonly namespace?: string;\n readonly name?: string;\n readonly attachTo: ExtensionDefinitionAttachTo;\n readonly disabled: boolean;\n readonly configSchema?: PortableSchema<any, any>;\n readonly inputs: {\n [inputName in string]: {\n $$type: '@backstage/ExtensionInput';\n extensionData: {\n [name in string]: ExtensionDataRef;\n };\n config: { optional: boolean; singleton: boolean };\n };\n };\n readonly output: {\n [name in string]: ExtensionDataRef;\n };\n factory(context: {\n node: AppNode;\n apis: ApiHolder;\n config: object;\n inputs: {\n [inputName in string]: unknown;\n };\n }): {\n [inputName in string]: unknown;\n };\n }\n | {\n readonly version: 'v2';\n readonly kind?: string;\n readonly namespace?: string;\n readonly name?: string;\n readonly attachTo: ExtensionDefinitionAttachTo;\n readonly disabled: boolean;\n readonly configSchema?: PortableSchema<any, any>;\n readonly inputs: { [inputName in string]: ExtensionInput };\n readonly output: Array<ExtensionDataRef>;\n factory(context: {\n node: AppNode;\n apis: ApiHolder;\n config: object;\n inputs: ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput;\n }>;\n }): Iterable<ExtensionDataValue<any, any>>;\n };\n}>({\n type: '@backstage/ExtensionDefinition',\n versions: ['v1', 'v2'],\n});\n"],"names":[],"mappings":";;AA8BO,MAAM,yBAAA,GAA4B,WAAW,MAAA,CAqDjD;AAAA,EACD,IAAA,EAAM,gCAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAA,EAAM,IAAI;AACvB,CAAC;;;;"}
1
+ {"version":3,"file":"InternalExtensionDefinition.esm.js","sources":["../../../../../frontend-internal/src/wiring/InternalExtensionDefinition.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ApiHolder,\n AppNode,\n ExtensionDefinitionAttachTo,\n ExtensionDataValue,\n ExtensionDataRef,\n OverridableExtensionDefinition,\n ExtensionDefinitionParameters,\n ExtensionInput,\n PortableSchema,\n} from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { ResolvedExtensionInputs } from '../../../frontend-plugin-api/src/wiring/createExtension';\nimport { OpaqueType } from '@internal/opaque';\nimport { FilterPredicate } from '@backstage/filter-predicates';\n\nexport const OpaqueExtensionDefinition = OpaqueType.create<{\n public: OverridableExtensionDefinition<ExtensionDefinitionParameters>;\n versions:\n | {\n readonly version: 'v1';\n readonly kind?: string;\n readonly namespace?: string;\n readonly name?: string;\n readonly attachTo: ExtensionDefinitionAttachTo;\n readonly disabled: boolean;\n readonly configSchema?: PortableSchema<any, any>;\n readonly inputs: {\n [inputName in string]: {\n $$type: '@backstage/ExtensionInput';\n extensionData: {\n [name in string]: ExtensionDataRef;\n };\n config: { optional: boolean; singleton: boolean };\n };\n };\n readonly output: {\n [name in string]: ExtensionDataRef;\n };\n factory(context: {\n node: AppNode;\n apis: ApiHolder;\n config: object;\n inputs: {\n [inputName in string]: unknown;\n };\n }): {\n [inputName in string]: unknown;\n };\n }\n | {\n readonly version: 'v2';\n readonly kind?: string;\n readonly namespace?: string;\n readonly name?: string;\n readonly attachTo: ExtensionDefinitionAttachTo;\n readonly disabled: boolean;\n readonly if?: FilterPredicate;\n readonly configSchema?: PortableSchema<any, any>;\n readonly inputs: { [inputName in string]: ExtensionInput };\n readonly output: Array<ExtensionDataRef>;\n factory(context: {\n node: AppNode;\n apis: ApiHolder;\n config: object;\n inputs: ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput;\n }>;\n }): Iterable<ExtensionDataValue<any, any>>;\n };\n}>({\n type: '@backstage/ExtensionDefinition',\n versions: ['v1', 'v2'],\n});\n"],"names":[],"mappings":";;AAgCO,MAAM,yBAAA,GAA4B,WAAW,MAAA,CAsDjD;AAAA,EACD,IAAA,EAAM,gCAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAA,EAAM,IAAI;AACvB,CAAC;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"InternalFrontendPlugin.esm.js","sources":["../../../../../frontend-internal/src/wiring/InternalFrontendPlugin.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Extension,\n FeatureFlagConfig,\n IconElement,\n OverridableFrontendPlugin,\n} from '@backstage/frontend-plugin-api';\nimport { JsonObject } from '@backstage/types';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueFrontendPlugin = OpaqueType.create<{\n public: OverridableFrontendPlugin;\n versions: {\n readonly version: 'v1';\n readonly title?: string;\n readonly icon?: IconElement;\n readonly extensions: Extension<unknown>[];\n readonly featureFlags: FeatureFlagConfig[];\n readonly infoOptions?: {\n packageJson?: () => Promise<JsonObject>;\n manifest?: () => Promise<JsonObject>;\n };\n };\n}>({\n type: '@backstage/FrontendPlugin',\n versions: ['v1'],\n});\n"],"names":[],"mappings":";;AAyBO,MAAM,oBAAA,GAAuB,WAAW,MAAA,CAa5C;AAAA,EACD,IAAA,EAAM,2BAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC;;;;"}
1
+ {"version":3,"file":"InternalFrontendPlugin.esm.js","sources":["../../../../../frontend-internal/src/wiring/InternalFrontendPlugin.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n Extension,\n FeatureFlagConfig,\n IconElement,\n OverridableFrontendPlugin,\n} from '@backstage/frontend-plugin-api';\nimport { FilterPredicate } from '@backstage/filter-predicates';\nimport { JsonObject } from '@backstage/types';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueFrontendPlugin = OpaqueType.create<{\n public: OverridableFrontendPlugin;\n versions: {\n readonly version: 'v1';\n readonly title?: string;\n readonly icon?: IconElement;\n readonly extensions: Extension<unknown>[];\n readonly featureFlags: FeatureFlagConfig[];\n readonly if?: FilterPredicate;\n readonly infoOptions?: {\n packageJson?: () => Promise<JsonObject>;\n manifest?: () => Promise<JsonObject>;\n };\n };\n}>({\n type: '@backstage/FrontendPlugin',\n versions: ['v1'],\n});\n"],"names":[],"mappings":";;AA0BO,MAAM,oBAAA,GAAuB,WAAW,MAAA,CAc5C;AAAA,EACD,IAAA,EAAM,2BAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAI;AACjB,CAAC;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"createFrontendModule.esm.js","sources":["../../../../../frontend-plugin-api/src/wiring/createFrontendModule.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OpaqueExtensionDefinition } from '@internal/frontend';\nimport { ExtensionDefinition } from './createExtension';\nimport {\n Extension,\n resolveExtensionDefinition,\n} from './resolveExtensionDefinition';\nimport { FeatureFlagConfig } from './types';\n\n/** @public */\nexport interface CreateFrontendModuleOptions<\n TPluginId extends string,\n TExtensions extends readonly ExtensionDefinition[],\n> {\n pluginId: TPluginId;\n extensions?: TExtensions;\n featureFlags?: FeatureFlagConfig[];\n}\n\n/** @public */\nexport interface FrontendModule {\n readonly $$type: '@backstage/FrontendModule';\n readonly pluginId: string;\n}\n\n/** @internal */\nexport interface InternalFrontendModule extends FrontendModule {\n readonly version: 'v1';\n readonly extensions: Extension<unknown>[];\n readonly featureFlags: FeatureFlagConfig[];\n}\n\n/**\n * Creates a new module that can be installed in a Backstage app.\n *\n * @remarks\n *\n * Modules are used to add or override extensions for an existing plugin. If a\n * module provides an extension with the same ID as one provided by the plugin,\n * the extension provided by the module will always take precedence.\n *\n * Every module is created for a specific plugin by providing the\n * unique ID of the plugin that the module should be installed for. If that\n * plugin is not present in the app, the module will be ignored and have no\n * effect.\n *\n * For more information on how modules work, see the\n * {@link https://backstage.io/docs/frontend-system/architecture/extension-overrides#creating-a-frontend-module | documentation for modules}\n * in the frontend system documentation.\n *\n * It is recommended to name the module variable of the form `<pluginId>Module<ModuleName>`.\n *\n * @example\n *\n * ```tsx\n * import { createFrontendModule } from '@backstage/frontend-plugin-api';\n *\n * export const exampleModuleCustomPage = createFrontendModule({\n * pluginId: 'example',\n * extensions: [\n * // Overrides the default page for the 'example' plugin\n * PageBlueprint.make({\n * path: '/example',\n * loader: () => import('./CustomPage').then(m => <m.CustomPage />),\n * }),\n * ],\n * });\n * ```\n *\n * @public\n */\nexport function createFrontendModule<\n TId extends string,\n TExtensions extends readonly ExtensionDefinition[],\n>(options: CreateFrontendModuleOptions<TId, TExtensions>): FrontendModule {\n const { pluginId } = options;\n\n const extensions = new Array<Extension<any>>();\n const extensionDefinitionsById = new Map<\n string,\n typeof OpaqueExtensionDefinition.TInternal\n >();\n\n for (const def of options.extensions ?? []) {\n const internal = OpaqueExtensionDefinition.toInternal(def);\n const ext = resolveExtensionDefinition(def, { namespace: pluginId });\n extensions.push(ext);\n extensionDefinitionsById.set(ext.id, {\n ...internal,\n namespace: pluginId,\n });\n }\n\n if (extensions.length !== extensionDefinitionsById.size) {\n const extensionIds = extensions.map(e => e.id);\n const duplicates = Array.from(\n new Set(\n extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index),\n ),\n );\n // TODO(Rugvip): This could provide some more information about the kind + name of the extensions\n throw new Error(\n `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join(\n ', ',\n )}`,\n );\n }\n\n return {\n $$type: '@backstage/FrontendModule',\n version: 'v1',\n pluginId,\n featureFlags: options.featureFlags ?? [],\n extensions,\n toString() {\n return `Module{pluginId=${pluginId}}`;\n },\n } as InternalFrontendModule;\n}\n\n/** @internal */\nexport function isInternalFrontendModule(opaque: {\n $$type: string;\n}): opaque is InternalFrontendModule {\n if (opaque.$$type === '@backstage/FrontendModule') {\n // Make sure we throw if invalid\n toInternalFrontendModule(opaque as FrontendModule);\n return true;\n }\n return false;\n}\n\n/** @internal */\nexport function toInternalFrontendModule(\n plugin: FrontendModule,\n): InternalFrontendModule {\n const internal = plugin as InternalFrontendModule;\n if (internal.$$type !== '@backstage/FrontendModule') {\n throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`);\n }\n if (internal.version !== 'v1') {\n throw new Error(\n `Invalid plugin instance, bad version '${internal.version}'`,\n );\n }\n return internal;\n}\n"],"names":[],"mappings":"AAwIO,SAAS,yBAAyB,MAAA,EAEJ;AACnC,EAAA,IAAI,MAAA,CAAO,WAAW,2BAAA,EAA6B;AAEjD,IAAA,wBAAA,CAAyB,MAAwB,CAAA;AACjD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT;AAGO,SAAS,yBACd,MAAA,EACwB;AACxB,EAAA,MAAM,QAAA,GAAW,MAAA;AACjB,EAAA,IAAI,QAAA,CAAS,WAAW,2BAAA,EAA6B;AACnD,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mCAAA,EAAsC,QAAA,CAAS,MAAM,CAAA,CAAA,CAAG,CAAA;AAAA,EAC1E;AACA,EAAA,IAAI,QAAA,CAAS,YAAY,IAAA,EAAM;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,SAAS,OAAO,CAAA,CAAA;AAAA,KAC3D;AAAA,EACF;AACA,EAAA,OAAO,QAAA;AACT;;;;"}
1
+ {"version":3,"file":"createFrontendModule.esm.js","sources":["../../../../../frontend-plugin-api/src/wiring/createFrontendModule.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OpaqueExtensionDefinition } from '@internal/frontend';\nimport { ExtensionDefinition } from './createExtension';\nimport {\n Extension,\n resolveExtensionDefinition,\n} from './resolveExtensionDefinition';\nimport { FeatureFlagConfig } from './types';\nimport { FilterPredicate } from '@backstage/filter-predicates';\n\n/** @public */\nexport interface CreateFrontendModuleOptions<\n TPluginId extends string,\n TExtensions extends readonly ExtensionDefinition[],\n> {\n pluginId: TPluginId;\n extensions?: TExtensions;\n featureFlags?: FeatureFlagConfig[];\n if?: FilterPredicate;\n}\n\n/** @public */\nexport interface FrontendModule {\n readonly $$type: '@backstage/FrontendModule';\n readonly pluginId: string;\n}\n\n/** @internal */\nexport interface InternalFrontendModule extends FrontendModule {\n readonly version: 'v1';\n readonly extensions: Extension<unknown>[];\n readonly featureFlags: FeatureFlagConfig[];\n readonly if?: FilterPredicate;\n}\n\n/**\n * Creates a new module that can be installed in a Backstage app.\n *\n * @remarks\n *\n * Modules are used to add or override extensions for an existing plugin. If a\n * module provides an extension with the same ID as one provided by the plugin,\n * the extension provided by the module will always take precedence.\n *\n * Every module is created for a specific plugin by providing the\n * unique ID of the plugin that the module should be installed for. If that\n * plugin is not present in the app, the module will be ignored and have no\n * effect.\n *\n * For more information on how modules work, see the\n * {@link https://backstage.io/docs/frontend-system/architecture/extension-overrides#creating-a-frontend-module | documentation for modules}\n * in the frontend system documentation.\n *\n * It is recommended to name the module variable of the form `<pluginId>Module<ModuleName>`.\n *\n * @example\n *\n * ```tsx\n * import { createFrontendModule } from '@backstage/frontend-plugin-api';\n *\n * export const exampleModuleCustomPage = createFrontendModule({\n * pluginId: 'example',\n * extensions: [\n * // Overrides the default page for the 'example' plugin\n * PageBlueprint.make({\n * path: '/example',\n * loader: () => import('./CustomPage').then(m => <m.CustomPage />),\n * }),\n * ],\n * });\n * ```\n *\n * @public\n */\nexport function createFrontendModule<\n TId extends string,\n TExtensions extends readonly ExtensionDefinition[],\n>(options: CreateFrontendModuleOptions<TId, TExtensions>): FrontendModule {\n const { pluginId } = options;\n\n const extensions = new Array<Extension<any>>();\n const extensionDefinitionsById = new Map<\n string,\n typeof OpaqueExtensionDefinition.TInternal\n >();\n\n for (const def of options.extensions ?? []) {\n const internal = OpaqueExtensionDefinition.toInternal(def);\n const ext = resolveExtensionDefinition(def, { namespace: pluginId });\n extensions.push(ext);\n extensionDefinitionsById.set(ext.id, {\n ...internal,\n namespace: pluginId,\n });\n }\n\n if (extensions.length !== extensionDefinitionsById.size) {\n const extensionIds = extensions.map(e => e.id);\n const duplicates = Array.from(\n new Set(\n extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index),\n ),\n );\n // TODO(Rugvip): This could provide some more information about the kind + name of the extensions\n throw new Error(\n `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join(\n ', ',\n )}`,\n );\n }\n\n return {\n $$type: '@backstage/FrontendModule',\n version: 'v1',\n pluginId,\n featureFlags: options.featureFlags ?? [],\n if: options.if,\n extensions,\n toString() {\n return `Module{pluginId=${pluginId}}`;\n },\n } as InternalFrontendModule;\n}\n\n/** @internal */\nexport function isInternalFrontendModule(opaque: {\n $$type: string;\n}): opaque is InternalFrontendModule {\n if (opaque.$$type === '@backstage/FrontendModule') {\n // Make sure we throw if invalid\n toInternalFrontendModule(opaque as FrontendModule);\n return true;\n }\n return false;\n}\n\n/** @internal */\nexport function toInternalFrontendModule(\n plugin: FrontendModule,\n): InternalFrontendModule {\n const internal = plugin as InternalFrontendModule;\n if (internal.$$type !== '@backstage/FrontendModule') {\n throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`);\n }\n if (internal.version !== 'v1') {\n throw new Error(\n `Invalid plugin instance, bad version '${internal.version}'`,\n );\n }\n return internal;\n}\n"],"names":[],"mappings":"AA4IO,SAAS,yBAAyB,MAAA,EAEJ;AACnC,EAAA,IAAI,MAAA,CAAO,WAAW,2BAAA,EAA6B;AAEjD,IAAA,wBAAA,CAAyB,MAAwB,CAAA;AACjD,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT;AAGO,SAAS,yBACd,MAAA,EACwB;AACxB,EAAA,MAAM,QAAA,GAAW,MAAA;AACjB,EAAA,IAAI,QAAA,CAAS,WAAW,2BAAA,EAA6B;AACnD,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mCAAA,EAAsC,QAAA,CAAS,MAAM,CAAA,CAAA,CAAG,CAAA;AAAA,EAC1E;AACA,EAAA,IAAI,QAAA,CAAS,YAAY,IAAA,EAAM;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,SAAS,OAAO,CAAA,CAAA;AAAA,KAC3D;AAAA,EACF;AACA,EAAA,OAAO,QAAA;AACT;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolveExtensionDefinition.esm.js","sources":["../../../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ApiHolder, AppNode } from '../apis';\nimport {\n ExtensionDefinitionAttachTo,\n ExtensionDefinition,\n ExtensionDefinitionParameters,\n ResolvedExtensionInputs,\n} from './createExtension';\nimport { PortableSchema } from '../schema';\nimport { ExtensionInput } from './createExtensionInput';\nimport { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef';\nimport {\n OpaqueExtensionDefinition,\n OpaqueExtensionInput,\n} from '@internal/frontend';\n\n/** @public */\nexport type ExtensionAttachTo =\n | { id: string; input: string }\n | Array<{ id: string; input: string }>;\n\n/**\n * @deprecated Use {@link ExtensionAttachTo} instead.\n * @public\n */\nexport type ExtensionAttachToSpec = ExtensionAttachTo;\n\n/** @public */\nexport interface Extension<TConfig, TConfigInput = TConfig> {\n $$type: '@backstage/Extension';\n readonly id: string;\n readonly attachTo: ExtensionAttachToSpec;\n readonly disabled: boolean;\n readonly configSchema?: PortableSchema<TConfig, TConfigInput>;\n}\n\n/** @internal */\nexport type InternalExtension<TConfig, TConfigInput> = Extension<\n TConfig,\n TConfigInput\n> &\n (\n | {\n readonly version: 'v1';\n readonly inputs: {\n [inputName in string]: {\n $$type: '@backstage/ExtensionInput';\n extensionData: {\n [name in string]: ExtensionDataRef;\n };\n config: { optional: boolean; singleton: boolean };\n };\n };\n readonly output: {\n [name in string]: ExtensionDataRef;\n };\n factory(context: {\n apis: ApiHolder;\n node: AppNode;\n config: TConfig;\n inputs: {\n [inputName in string]: unknown;\n };\n }): {\n [inputName in string]: unknown;\n };\n }\n | {\n readonly version: 'v2';\n readonly inputs: { [inputName in string]: ExtensionInput };\n readonly output: Array<ExtensionDataRef>;\n factory(options: {\n apis: ApiHolder;\n node: AppNode;\n config: TConfig;\n inputs: ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput;\n }>;\n }): Iterable<ExtensionDataValue<any, any>>;\n }\n );\n\n/** @internal */\nexport function toInternalExtension<TConfig, TConfigInput>(\n overrides: Extension<TConfig, TConfigInput>,\n): InternalExtension<TConfig, TConfigInput> {\n const internal = overrides as InternalExtension<TConfig, TConfigInput>;\n if (internal.$$type !== '@backstage/Extension') {\n throw new Error(\n `Invalid extension instance, bad type '${internal.$$type}'`,\n );\n }\n const version = internal.version;\n if (version !== 'v1' && version !== 'v2') {\n throw new Error(`Invalid extension instance, bad version '${version}'`);\n }\n return internal;\n}\n\n/** @ignore */\nexport type ResolveExtensionId<\n TExtension extends ExtensionDefinition,\n TNamespace extends string,\n> = TExtension extends ExtensionDefinition<{\n kind: infer IKind extends string | undefined;\n name: infer IName extends string | undefined;\n params: any;\n}>\n ? [string] extends [IKind | IName]\n ? never\n : (\n undefined extends IName ? TNamespace : `${TNamespace}/${IName}`\n ) extends infer INamePart extends string\n ? IKind extends string\n ? `${IKind}:${INamePart}`\n : INamePart\n : never\n : never;\n\nfunction resolveExtensionId(\n kind?: string,\n namespace?: string,\n name?: string,\n): string {\n const namePart =\n name && namespace ? `${namespace}/${name}` : namespace || name;\n if (!namePart) {\n throw new Error(\n `Extension must declare an explicit namespace or name as it could not be resolved from context, kind=${kind} namespace=${namespace} name=${name}`,\n );\n }\n\n return kind ? `${kind}:${namePart}` : namePart;\n}\n\nfunction resolveAttachTo(\n attachTo: ExtensionDefinitionAttachTo | ExtensionDefinitionAttachTo[],\n namespace?: string,\n): ExtensionAttachToSpec {\n const resolveSpec = (\n spec: ExtensionDefinitionAttachTo,\n ): { id: string; input: string } => {\n if (OpaqueExtensionInput.isType(spec)) {\n const { context } = OpaqueExtensionInput.toInternal(spec);\n if (!context) {\n throw new Error(\n 'Invalid input object without a parent extension used as attachment point',\n );\n }\n return {\n id: resolveExtensionId(context.kind, namespace, context.name),\n input: context.input,\n };\n }\n if ('relative' in spec && spec.relative) {\n return {\n id: resolveExtensionId(\n spec.relative.kind,\n namespace,\n spec.relative.name,\n ),\n input: spec.input,\n };\n }\n if ('id' in spec) {\n return { id: spec.id, input: spec.input };\n }\n throw new Error('Invalid attachment point specification');\n };\n\n if (Array.isArray(attachTo)) {\n return attachTo.map(resolveSpec);\n }\n\n return resolveSpec(attachTo);\n}\n\n/** @internal */\nexport function resolveExtensionDefinition<\n T extends ExtensionDefinitionParameters,\n>(\n definition: ExtensionDefinition<T>,\n context?: { namespace?: string },\n): Extension<T['config'], T['configInput']> {\n const internalDefinition = OpaqueExtensionDefinition.toInternal(definition);\n\n const {\n name,\n kind,\n namespace: internalNamespace,\n override: _skip2,\n attachTo,\n ...rest\n } = internalDefinition;\n\n const namespace = internalNamespace ?? context?.namespace;\n const id = resolveExtensionId(kind, namespace, name);\n\n return {\n ...rest,\n attachTo: resolveAttachTo(attachTo, namespace),\n $$type: '@backstage/Extension',\n version: internalDefinition.version,\n id,\n toString() {\n return `Extension{id=${id}}`;\n },\n } as InternalExtension<T['config'], T['configInput']> & Object;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolveExtensionDefinition.esm.js","sources":["../../../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ApiHolder, AppNode } from '../apis';\nimport {\n ExtensionDefinitionAttachTo,\n ExtensionDefinition,\n ExtensionDefinitionParameters,\n ResolvedExtensionInputs,\n} from './createExtension';\nimport { PortableSchema } from '../schema';\nimport { ExtensionInput } from './createExtensionInput';\nimport { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef';\nimport {\n OpaqueExtensionDefinition,\n OpaqueExtensionInput,\n} from '@internal/frontend';\nimport { FilterPredicate } from '@backstage/filter-predicates';\n\n/** @public */\nexport type ExtensionAttachTo = { id: string; input: string };\n\n/** @public */\nexport interface Extension<TConfig, TConfigInput = TConfig> {\n $$type: '@backstage/Extension';\n readonly id: string;\n readonly attachTo: ExtensionAttachTo;\n readonly disabled: boolean;\n readonly configSchema?: PortableSchema<TConfig, TConfigInput>;\n}\n\n/** @internal */\nexport type InternalExtension<TConfig, TConfigInput> = Extension<\n TConfig,\n TConfigInput\n> &\n (\n | {\n readonly version: 'v1';\n readonly inputs: {\n [inputName in string]: {\n $$type: '@backstage/ExtensionInput';\n extensionData: {\n [name in string]: ExtensionDataRef;\n };\n config: { optional: boolean; singleton: boolean };\n };\n };\n readonly output: {\n [name in string]: ExtensionDataRef;\n };\n factory(context: {\n apis: ApiHolder;\n node: AppNode;\n config: TConfig;\n inputs: {\n [inputName in string]: unknown;\n };\n }): {\n [inputName in string]: unknown;\n };\n }\n | {\n readonly version: 'v2';\n readonly if?: FilterPredicate;\n readonly inputs: { [inputName in string]: ExtensionInput };\n readonly output: Array<ExtensionDataRef>;\n factory(options: {\n apis: ApiHolder;\n node: AppNode;\n config: TConfig;\n inputs: ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput;\n }>;\n }): Iterable<ExtensionDataValue<any, any>>;\n }\n );\n\n/** @internal */\nexport function toInternalExtension<TConfig, TConfigInput>(\n overrides: Extension<TConfig, TConfigInput>,\n): InternalExtension<TConfig, TConfigInput> {\n const internal = overrides as InternalExtension<TConfig, TConfigInput>;\n if (internal.$$type !== '@backstage/Extension') {\n throw new Error(\n `Invalid extension instance, bad type '${internal.$$type}'`,\n );\n }\n const version = internal.version;\n if (version !== 'v1' && version !== 'v2') {\n throw new Error(`Invalid extension instance, bad version '${version}'`);\n }\n return internal;\n}\n\n/** @ignore */\nexport type ResolveExtensionId<\n TExtension extends ExtensionDefinition,\n TNamespace extends string,\n> = TExtension extends ExtensionDefinition<{\n kind: infer IKind extends string | undefined;\n name: infer IName extends string | undefined;\n params: any;\n}>\n ? [string] extends [IKind | IName]\n ? never\n : (\n undefined extends IName ? TNamespace : `${TNamespace}/${IName}`\n ) extends infer INamePart extends string\n ? IKind extends string\n ? `${IKind}:${INamePart}`\n : INamePart\n : never\n : never;\n\nfunction resolveExtensionId(\n kind?: string,\n namespace?: string,\n name?: string,\n): string {\n const namePart =\n name && namespace ? `${namespace}/${name}` : namespace || name;\n if (!namePart) {\n throw new Error(\n `Extension must declare an explicit namespace or name as it could not be resolved from context, kind=${kind} namespace=${namespace} name=${name}`,\n );\n }\n\n return kind ? `${kind}:${namePart}` : namePart;\n}\n\nfunction resolveAttachTo(\n attachTo: ExtensionDefinitionAttachTo | ExtensionDefinitionAttachTo[],\n namespace?: string,\n): ExtensionAttachTo | ExtensionAttachTo[] {\n const resolveSpec = (\n spec: ExtensionDefinitionAttachTo,\n ): { id: string; input: string } => {\n if (OpaqueExtensionInput.isType(spec)) {\n const { context } = OpaqueExtensionInput.toInternal(spec);\n if (!context) {\n throw new Error(\n 'Invalid input object without a parent extension used as attachment point',\n );\n }\n return {\n id: resolveExtensionId(context.kind, namespace, context.name),\n input: context.input,\n };\n }\n if ('relative' in spec && spec.relative) {\n return {\n id: resolveExtensionId(\n spec.relative.kind,\n namespace,\n spec.relative.name,\n ),\n input: spec.input,\n };\n }\n if ('id' in spec) {\n return { id: spec.id, input: spec.input };\n }\n throw new Error('Invalid attachment point specification');\n };\n\n if (Array.isArray(attachTo)) {\n return attachTo.map(resolveSpec);\n }\n\n return resolveSpec(attachTo);\n}\n\n/** @internal */\nexport function resolveExtensionDefinition<\n T extends ExtensionDefinitionParameters,\n>(\n definition: ExtensionDefinition<T>,\n context?: { namespace?: string },\n): Extension<T['config'], T['configInput']> {\n const internalDefinition = OpaqueExtensionDefinition.toInternal(definition);\n\n const {\n name,\n kind,\n namespace: internalNamespace,\n override: _skip2,\n attachTo,\n ...rest\n } = internalDefinition;\n\n const namespace = internalNamespace ?? context?.namespace;\n const id = resolveExtensionId(kind, namespace, name);\n\n return {\n ...rest,\n attachTo: resolveAttachTo(attachTo, namespace) as ExtensionAttachTo,\n $$type: '@backstage/Extension',\n version: internalDefinition.version,\n id,\n toString() {\n return `Extension{id=${id}}`;\n },\n } as InternalExtension<T['config'], T['configInput']> & Object;\n}\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { ExternalRouteRef, RouteRef, SubRouteRef, FrontendPluginInfo, FrontendPlugin, AppNode, FrontendFeature, ExtensionFactoryMiddleware, AppTree } from '@backstage/frontend-plugin-api';
2
- import { ConfigApi, ApiHolder } from '@backstage/core-plugin-api';
1
+ import { ExternalRouteRef, RouteRef, SubRouteRef, FrontendPluginInfo, FrontendPlugin, AppNode, AppTree, FrontendFeature, ConfigApi, ExtensionFactoryMiddleware as ExtensionFactoryMiddleware$1, ApiHolder, ExtensionDataContainer, ExtensionDataRef, ExtensionDataValue } from '@backstage/frontend-plugin-api';
3
2
  import { JsonObject } from '@backstage/types';
4
3
 
5
4
  /**
@@ -157,6 +156,26 @@ type AppErrorTypes = {
157
156
  existingPluginId: string;
158
157
  };
159
158
  };
159
+ EXTENSION_BOOTSTRAP_PREDICATE_IGNORED: {
160
+ context: {
161
+ node: AppNode;
162
+ };
163
+ };
164
+ EXTENSION_BOOTSTRAP_API_UNAVAILABLE: {
165
+ context: {
166
+ node: AppNode;
167
+ apiRefId: string;
168
+ };
169
+ };
170
+ EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED: {
171
+ context: {
172
+ node: AppNode;
173
+ apiRefId: string;
174
+ bootstrapNode: AppNode;
175
+ pluginId: string;
176
+ bootstrapPluginId: string;
177
+ };
178
+ };
160
179
  ROUTE_DUPLICATE: {
161
180
  context: {
162
181
  routeId: string;
@@ -183,11 +202,39 @@ type AppError = keyof AppErrorTypes extends infer ICode extends keyof AppErrorTy
183
202
  } : never : never;
184
203
 
185
204
  /**
186
- * Options for {@link createSpecializedApp}.
205
+ * Result of bootstrapping a prepared specialized app.
187
206
  *
188
207
  * @public
189
208
  */
190
- type CreateSpecializedAppOptions = {
209
+ type BootstrapSpecializedApp = {
210
+ element: JSX.Element;
211
+ tree: AppTree;
212
+ };
213
+ /**
214
+ * Result of finalizing a prepared specialized app.
215
+ *
216
+ * @public
217
+ */
218
+ type FinalizedSpecializedApp = {
219
+ element: JSX.Element;
220
+ sessionState: SpecializedAppSessionState;
221
+ tree: AppTree;
222
+ errors?: AppError[];
223
+ };
224
+ /**
225
+ * Opaque reusable session state for specialized apps.
226
+ *
227
+ * @public
228
+ */
229
+ type SpecializedAppSessionState = {
230
+ $$type: '@backstage/SpecializedAppSessionState';
231
+ };
232
+ /**
233
+ * Options for {@link prepareSpecializedApp}.
234
+ *
235
+ * @public
236
+ */
237
+ type PrepareSpecializedAppOptions = {
191
238
  /**
192
239
  * The list of features to load.
193
240
  */
@@ -212,24 +259,86 @@ type CreateSpecializedAppOptions = {
212
259
  */
213
260
  advanced?: {
214
261
  /**
215
- * A replacement API holder implementation to use.
262
+ * A reusable specialized app session state to use.
216
263
  *
217
- * By default, a new API holder will be constructed automatically based on
218
- * the other inputs. If you pass in a custom one here, none of that
219
- * automation will take place - so you will have to take care to supply all
220
- * those APIs yourself.
264
+ * This can be obtained from either the app passed to
265
+ * {@link PreparedSpecializedApp.onFinalized} or from
266
+ * {@link PreparedSpecializedApp.finalize}, and reused in a future app
267
+ * instance to skip sign-in and session preparation.
221
268
  */
222
- apis?: ApiHolder;
269
+ sessionState?: SpecializedAppSessionState;
223
270
  /**
224
- * If set to true, the system will silently accept and move on if
225
- * encountering config for extensions that do not exist. The default is to
226
- * reject such config to help catch simple mistakes.
271
+ * Applies one or more middleware on every extension, as they are added to
272
+ * the application.
227
273
  *
228
- * This flag can be useful in some scenarios where you have a dynamic set of
229
- * extensions enabled at different times, but also increases the risk of
230
- * accidentally missing e.g. simple typos in your config.
274
+ * This is an advanced use case for modifying extension data on the fly as
275
+ * it gets emitted by extensions being instantiated.
276
+ */
277
+ extensionFactoryMiddleware?: ExtensionFactoryMiddleware$1 | ExtensionFactoryMiddleware$1[];
278
+ /**
279
+ * Allows for customizing how plugin info is retrieved.
280
+ */
281
+ pluginInfoResolver?: FrontendPluginInfoResolver;
282
+ };
283
+ };
284
+ /**
285
+ * Result of {@link prepareSpecializedApp}.
286
+ *
287
+ * @public
288
+ */
289
+ type PreparedSpecializedApp = {
290
+ getBootstrapApp(): BootstrapSpecializedApp;
291
+ onFinalized(callback: (app: FinalizedSpecializedApp) => void): () => void;
292
+ finalize(): FinalizedSpecializedApp;
293
+ };
294
+ /**
295
+ * Prepares an app without instantiating the full extension tree.
296
+ *
297
+ * @remarks
298
+ *
299
+ * This is useful for split sign-in flows where the sign-in page should be
300
+ * rendered first, and the full app finalized once an identity has been
301
+ * captured.
302
+ *
303
+ * @public
304
+ */
305
+ declare function prepareSpecializedApp(options?: PrepareSpecializedAppOptions): PreparedSpecializedApp;
306
+
307
+ /**
308
+ * Options for {@link createSpecializedApp}.
309
+ *
310
+ * @deprecated Use `PrepareSpecializedAppOptions` with `prepareSpecializedApp` instead.
311
+ *
312
+ * @public
313
+ */
314
+ type CreateSpecializedAppOptions = {
315
+ /**
316
+ * The list of features to load.
317
+ */
318
+ features?: FrontendFeature[];
319
+ /**
320
+ * The config API implementation to use. For most normal apps, this should be
321
+ * specified.
322
+ *
323
+ * If none is given, a new _empty_ config will be used during startup. In
324
+ * later stages of the app lifecycle, the config API in the API holder will be
325
+ * used.
326
+ */
327
+ config?: ConfigApi;
328
+ /**
329
+ * Allows for the binding of plugins' external route refs within the app.
330
+ */
331
+ bindRoutes?(context: {
332
+ bind: CreateAppRouteBinder;
333
+ }): void;
334
+ /**
335
+ * Advanced, more rarely used options.
336
+ */
337
+ advanced?: {
338
+ /**
339
+ * APIs to expose to the app during startup.
231
340
  */
232
- allowUnknownExtensionConfig?: boolean;
341
+ apis?: ApiHolder;
233
342
  /**
234
343
  * Applies one or more middleware on every extension, as they are added to
235
344
  * the application.
@@ -237,7 +346,7 @@ type CreateSpecializedAppOptions = {
237
346
  * This is an advanced use case for modifying extension data on the fly as
238
347
  * it gets emitted by extensions being instantiated.
239
348
  */
240
- extensionFactoryMiddleware?: ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[];
349
+ extensionFactoryMiddleware?: ExtensionFactoryMiddleware$1 | ExtensionFactoryMiddleware$1[];
241
350
  /**
242
351
  * Allows for customizing how plugin info is retrieved.
243
352
  */
@@ -249,13 +358,20 @@ type CreateSpecializedAppOptions = {
249
358
  * intended for use in tests or specialized setups. Typically you want to use
250
359
  * `createApp` from `@backstage/frontend-defaults` instead.
251
360
  *
361
+ * @deprecated Use `prepareSpecializedApp` instead.
362
+ *
252
363
  * @public
253
364
  */
254
- declare function createSpecializedApp(options?: CreateSpecializedAppOptions): {
365
+ declare function createSpecializedApp(options?: CreateSpecializedAppOptions): FinalizedSpecializedApp;
366
+
367
+ /** @public */
368
+ type ExtensionFactoryMiddleware = (originalFactory: (contextOverrides?: {
369
+ config?: JsonObject;
370
+ }) => ExtensionDataContainer<ExtensionDataRef>, context: {
371
+ node: AppNode;
255
372
  apis: ApiHolder;
256
- tree: AppTree;
257
- errors?: AppError[];
258
- };
373
+ config?: JsonObject;
374
+ }) => Iterable<ExtensionDataValue<any, any>>;
259
375
 
260
- export { createSpecializedApp };
261
- export type { AppError, AppErrorTypes, CreateAppRouteBinder, CreateSpecializedAppOptions, FrontendPluginInfoResolver };
376
+ export { createSpecializedApp, prepareSpecializedApp };
377
+ export type { AppError, AppErrorTypes, BootstrapSpecializedApp, CreateAppRouteBinder, CreateSpecializedAppOptions, ExtensionFactoryMiddleware, FinalizedSpecializedApp, FrontendPluginInfoResolver, PrepareSpecializedAppOptions, PreparedSpecializedApp, SpecializedAppSessionState };
package/dist/index.esm.js CHANGED
@@ -1,2 +1,3 @@
1
+ export { prepareSpecializedApp } from './wiring/prepareSpecializedApp.esm.js';
1
2
  export { createSpecializedApp } from './wiring/createSpecializedApp.esm.js';
2
3
  //# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,6 +1,4 @@
1
1
  import { OpaqueRouteRef } from '../frontend-internal/src/routing/OpaqueRouteRef.esm.js';
2
- import '../frontend-internal/src/routing/OpaqueSubRouteRef.esm.js';
3
- import '../frontend-internal/src/routing/OpaqueExternalRouteRef.esm.js';
4
2
 
5
3
  function createRouteAliasResolver(routeRefsById) {
6
4
  const resolver = (routeRef, pluginId) => {
@@ -1 +1 @@
1
- {"version":3,"file":"RouteAliasResolver.esm.js","sources":["../../src/routing/RouteAliasResolver.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RouteRef } from '@backstage/frontend-plugin-api';\nimport { RouteRefsById } from './collectRouteIds';\nimport { OpaqueRouteRef } from '@internal/frontend';\n\n/**\n * @internal\n */\nexport type RouteAliasResolver = {\n (routeRef: RouteRef, pluginId?: string): RouteRef;\n (routeRef?: RouteRef, pluginId?: string): RouteRef | undefined;\n};\n\n/**\n * Creates a route alias resolver that resolves aliases based on the route IDs\n * @internal\n */\nexport function createRouteAliasResolver(\n routeRefsById: RouteRefsById,\n): RouteAliasResolver {\n const resolver = (routeRef: RouteRef | undefined, pluginId?: string) => {\n if (!routeRef) {\n return undefined;\n }\n\n let currentRef = routeRef;\n for (let i = 0; i < 100; i++) {\n const alias = OpaqueRouteRef.toInternal(currentRef).alias;\n if (alias) {\n if (pluginId) {\n const [aliasPluginId] = alias.split('.');\n if (aliasPluginId !== pluginId) {\n throw new Error(\n `Refused to resolve alias '${alias}' for ${currentRef} as it points to a different plugin, the expected plugin is '${pluginId}' but the alias points to '${aliasPluginId}'`,\n );\n }\n }\n const aliasRef = routeRefsById.routes.get(alias);\n if (!aliasRef) {\n throw new Error(\n `Unable to resolve RouteRef alias '${alias}' for ${currentRef}`,\n );\n }\n if (aliasRef.$$type === '@backstage/SubRouteRef') {\n throw new Error(\n `RouteRef alias '${alias}' for ${currentRef} points to a SubRouteRef, which is not supported`,\n );\n }\n currentRef = aliasRef;\n } else {\n return currentRef;\n }\n }\n throw new Error(`Alias loop detected for ${routeRef}`);\n };\n\n return resolver as RouteAliasResolver;\n}\n\n/**\n * Creates a route alias resolver that resolves aliases based on a map of route refs to their aliases\n * @internal\n */\nexport function createExactRouteAliasResolver(\n routeAliases: Map<RouteRef, RouteRef | undefined>,\n): RouteAliasResolver {\n const resolver = (routeRef?: RouteRef) => {\n if (routeRef && routeAliases.has(routeRef)) {\n return routeAliases.get(routeRef);\n }\n return routeRef;\n };\n return resolver as RouteAliasResolver;\n}\n"],"names":[],"mappings":";;;;AAgCO,SAAS,yBACd,aAAA,EACoB;AACpB,EAAA,MAAM,QAAA,GAAW,CAAC,QAAA,EAAgC,QAAA,KAAsB;AACtE,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAI,UAAA,GAAa,QAAA;AACjB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,MAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,UAAA,CAAW,UAAU,CAAA,CAAE,KAAA;AACpD,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,IAAI,QAAA,EAAU;AACZ,UAAA,MAAM,CAAC,aAAa,CAAA,GAAI,KAAA,CAAM,MAAM,GAAG,CAAA;AACvC,UAAA,IAAI,kBAAkB,QAAA,EAAU;AAC9B,YAAA,MAAM,IAAI,KAAA;AAAA,cACR,6BAA6B,KAAK,CAAA,MAAA,EAAS,UAAU,CAAA,6DAAA,EAAgE,QAAQ,8BAA8B,aAAa,CAAA,CAAA;AAAA,aAC1K;AAAA,UACF;AAAA,QACF;AACA,QAAA,MAAM,QAAA,GAAW,aAAA,CAAc,MAAA,CAAO,GAAA,CAAI,KAAK,CAAA;AAC/C,QAAA,IAAI,CAAC,QAAA,EAAU;AACb,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,kCAAA,EAAqC,KAAK,CAAA,MAAA,EAAS,UAAU,CAAA;AAAA,WAC/D;AAAA,QACF;AACA,QAAA,IAAI,QAAA,CAAS,WAAW,wBAAA,EAA0B;AAChD,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,gBAAA,EAAmB,KAAK,CAAA,MAAA,EAAS,UAAU,CAAA,gDAAA;AAAA,WAC7C;AAAA,QACF;AACA,QAAA,UAAA,GAAa,QAAA;AAAA,MACf,CAAA,MAAO;AACL,QAAA,OAAO,UAAA;AAAA,MACT;AAAA,IACF;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,QAAQ,CAAA,CAAE,CAAA;AAAA,EACvD,CAAA;AAEA,EAAA,OAAO,QAAA;AACT;AAMO,SAAS,8BACd,YAAA,EACoB;AACpB,EAAA,MAAM,QAAA,GAAW,CAAC,QAAA,KAAwB;AACxC,IAAA,IAAI,QAAA,IAAY,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,EAAG;AAC1C,MAAA,OAAO,YAAA,CAAa,IAAI,QAAQ,CAAA;AAAA,IAClC;AACA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACA,EAAA,OAAO,QAAA;AACT;;;;"}
1
+ {"version":3,"file":"RouteAliasResolver.esm.js","sources":["../../src/routing/RouteAliasResolver.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RouteRef } from '@backstage/frontend-plugin-api';\nimport { RouteRefsById } from './collectRouteIds';\nimport { OpaqueRouteRef } from '@internal/frontend';\n\n/**\n * @internal\n */\nexport type RouteAliasResolver = {\n (routeRef: RouteRef, pluginId?: string): RouteRef;\n (routeRef?: RouteRef, pluginId?: string): RouteRef | undefined;\n};\n\n/**\n * Creates a route alias resolver that resolves aliases based on the route IDs\n * @internal\n */\nexport function createRouteAliasResolver(\n routeRefsById: RouteRefsById,\n): RouteAliasResolver {\n const resolver = (routeRef: RouteRef | undefined, pluginId?: string) => {\n if (!routeRef) {\n return undefined;\n }\n\n let currentRef = routeRef;\n for (let i = 0; i < 100; i++) {\n const alias = OpaqueRouteRef.toInternal(currentRef).alias;\n if (alias) {\n if (pluginId) {\n const [aliasPluginId] = alias.split('.');\n if (aliasPluginId !== pluginId) {\n throw new Error(\n `Refused to resolve alias '${alias}' for ${currentRef} as it points to a different plugin, the expected plugin is '${pluginId}' but the alias points to '${aliasPluginId}'`,\n );\n }\n }\n const aliasRef = routeRefsById.routes.get(alias);\n if (!aliasRef) {\n throw new Error(\n `Unable to resolve RouteRef alias '${alias}' for ${currentRef}`,\n );\n }\n if (aliasRef.$$type === '@backstage/SubRouteRef') {\n throw new Error(\n `RouteRef alias '${alias}' for ${currentRef} points to a SubRouteRef, which is not supported`,\n );\n }\n currentRef = aliasRef;\n } else {\n return currentRef;\n }\n }\n throw new Error(`Alias loop detected for ${routeRef}`);\n };\n\n return resolver as RouteAliasResolver;\n}\n\n/**\n * Creates a route alias resolver that resolves aliases based on a map of route refs to their aliases\n * @internal\n */\nexport function createExactRouteAliasResolver(\n routeAliases: Map<RouteRef, RouteRef | undefined>,\n): RouteAliasResolver {\n const resolver = (routeRef?: RouteRef) => {\n if (routeRef && routeAliases.has(routeRef)) {\n return routeAliases.get(routeRef);\n }\n return routeRef;\n };\n return resolver as RouteAliasResolver;\n}\n"],"names":[],"mappings":";;AAgCO,SAAS,yBACd,aAAA,EACoB;AACpB,EAAA,MAAM,QAAA,GAAW,CAAC,QAAA,EAAgC,QAAA,KAAsB;AACtE,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAI,UAAA,GAAa,QAAA;AACjB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,MAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,UAAA,CAAW,UAAU,CAAA,CAAE,KAAA;AACpD,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,IAAI,QAAA,EAAU;AACZ,UAAA,MAAM,CAAC,aAAa,CAAA,GAAI,KAAA,CAAM,MAAM,GAAG,CAAA;AACvC,UAAA,IAAI,kBAAkB,QAAA,EAAU;AAC9B,YAAA,MAAM,IAAI,KAAA;AAAA,cACR,6BAA6B,KAAK,CAAA,MAAA,EAAS,UAAU,CAAA,6DAAA,EAAgE,QAAQ,8BAA8B,aAAa,CAAA,CAAA;AAAA,aAC1K;AAAA,UACF;AAAA,QACF;AACA,QAAA,MAAM,QAAA,GAAW,aAAA,CAAc,MAAA,CAAO,GAAA,CAAI,KAAK,CAAA;AAC/C,QAAA,IAAI,CAAC,QAAA,EAAU;AACb,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,kCAAA,EAAqC,KAAK,CAAA,MAAA,EAAS,UAAU,CAAA;AAAA,WAC/D;AAAA,QACF;AACA,QAAA,IAAI,QAAA,CAAS,WAAW,wBAAA,EAA0B;AAChD,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,gBAAA,EAAmB,KAAK,CAAA,MAAA,EAAS,UAAU,CAAA,gDAAA;AAAA,WAC7C;AAAA,QACF;AACA,QAAA,UAAA,GAAa,QAAA;AAAA,MACf,CAAA,MAAO;AACL,QAAA,OAAO,UAAA;AAAA,MACT;AAAA,IACF;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,QAAQ,CAAA,CAAE,CAAA;AAAA,EACvD,CAAA;AAEA,EAAA,OAAO,QAAA;AACT;AAMO,SAAS,8BACd,YAAA,EACoB;AACpB,EAAA,MAAM,QAAA,GAAW,CAAC,QAAA,KAAwB;AACxC,IAAA,IAAI,QAAA,IAAY,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,EAAG;AAC1C,MAAA,OAAO,YAAA,CAAa,IAAI,QAAQ,CAAA;AAAA,IAClC;AACA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACA,EAAA,OAAO,QAAA;AACT;;;;"}
@@ -1,8 +1,8 @@
1
1
  import { generatePath, matchRoutes } from 'react-router-dom';
2
2
  import mapValues from 'lodash/mapValues';
3
3
  import { OpaqueExternalRouteRef } from '../frontend-internal/src/routing/OpaqueExternalRouteRef.esm.js';
4
- import { OpaqueRouteRef } from '../frontend-internal/src/routing/OpaqueRouteRef.esm.js';
5
4
  import { OpaqueSubRouteRef } from '../frontend-internal/src/routing/OpaqueSubRouteRef.esm.js';
5
+ import { OpaqueRouteRef } from '../frontend-internal/src/routing/OpaqueRouteRef.esm.js';
6
6
 
7
7
  function joinPaths(...paths) {
8
8
  const normalized = paths.join("/").replace(/\/\/+/g, "/");
@@ -1,4 +1,6 @@
1
1
  import { OpaqueExternalRouteRef } from '../frontend-internal/src/routing/OpaqueExternalRouteRef.esm.js';
2
+ import '../frontend-internal/src/routing/OpaqueRouteRef.esm.js';
3
+ import '../frontend-internal/src/routing/OpaqueSubRouteRef.esm.js';
2
4
 
3
5
  function resolveRouteBindings(bindRoutes, config, routesById, collector) {
4
6
  const result = /* @__PURE__ */ new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"resolveRouteBindings.esm.js","sources":["../../src/routing/resolveRouteBindings.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n RouteRef,\n SubRouteRef,\n ExternalRouteRef,\n} from '@backstage/frontend-plugin-api';\nimport { RouteRefsById } from './collectRouteIds';\nimport { ErrorCollector } from '../wiring/createErrorCollector';\nimport { Config } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { OpaqueExternalRouteRef } from '@internal/frontend';\n\n/**\n * Extracts a union of the keys in a map whose value extends the given type\n *\n * @ignore\n */\ntype KeysWithType<Obj extends { [key in string]: any }, Type> = {\n [key in keyof Obj]: Obj[key] extends Type ? key : never;\n}[keyof Obj];\n\n/**\n * Takes a map Map required values and makes all keys matching Keys optional\n *\n * @ignore\n */\ntype PartialKeys<\n Map extends { [name in string]: any },\n Keys extends keyof Map,\n> = Partial<Pick<Map, Keys>> & Required<Omit<Map, Keys>>;\n\n/**\n * Creates a map of target routes with matching parameters based on a map of external routes.\n *\n * @ignore\n */\ntype TargetRouteMap<\n ExternalRoutes extends { [name: string]: ExternalRouteRef },\n> = {\n [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef<\n infer Params\n >\n ? RouteRef<Params> | SubRouteRef<Params> | false\n : never;\n};\n\n/**\n * A function that can bind from external routes of a given plugin, to concrete\n * routes of other plugins. See {@link @backstage/frontend-defaults#createApp}.\n *\n * @public\n */\nexport type CreateAppRouteBinder = <\n TExternalRoutes extends { [name: string]: ExternalRouteRef },\n>(\n externalRoutes: TExternalRoutes,\n targetRoutes: PartialKeys<\n TargetRouteMap<TExternalRoutes>,\n KeysWithType<TExternalRoutes, ExternalRouteRef<any>>\n >,\n) => void;\n\n/** @internal */\nexport function resolveRouteBindings(\n bindRoutes: ((context: { bind: CreateAppRouteBinder }) => void) | undefined,\n config: Config,\n routesById: RouteRefsById,\n collector: ErrorCollector,\n): Map<ExternalRouteRef, RouteRef | SubRouteRef> {\n const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();\n const disabledExternalRefs = new Set<ExternalRouteRef>();\n\n // Perform callback bindings first with highest priority\n if (bindRoutes) {\n const bind: CreateAppRouteBinder = (\n externalRoutes,\n targetRoutes: { [name: string]: RouteRef | SubRouteRef },\n ) => {\n for (const [key, value] of Object.entries(targetRoutes)) {\n const externalRoute = externalRoutes[key];\n if (!externalRoute) {\n collector.report({\n code: 'ROUTE_NOT_FOUND',\n message: `Key ${key} is not an existing external route`,\n context: { routeId: String(key) },\n });\n continue;\n }\n if (value) {\n result.set(externalRoute, value);\n } else if (value === false) {\n disabledExternalRefs.add(externalRoute);\n }\n }\n };\n bindRoutes({ bind });\n }\n\n // Then perform config based bindings with lower priority\n const bindings = config\n .getOptionalConfig('app.routes.bindings')\n ?.get<JsonObject>();\n if (bindings) {\n for (const [externalRefId, targetRefId] of Object.entries(bindings)) {\n if (!isValidTargetRefId(targetRefId)) {\n collector.report({\n code: 'ROUTE_BINDING_INVALID_VALUE',\n message: `Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string or false`,\n context: { routeId: externalRefId },\n });\n continue;\n }\n\n const externalRef = routesById.externalRoutes.get(externalRefId);\n if (!externalRef) {\n collector.report({\n code: 'ROUTE_NOT_FOUND',\n message: `Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`,\n context: { routeId: externalRefId },\n });\n continue;\n }\n\n // Skip if binding was already defined in code\n if (result.has(externalRef) || disabledExternalRefs.has(externalRef)) {\n continue;\n }\n\n if (targetRefId === false) {\n disabledExternalRefs.add(externalRef);\n } else {\n const targetRef = routesById.routes.get(targetRefId);\n if (!targetRef) {\n collector.report({\n code: 'ROUTE_NOT_FOUND',\n message: `Invalid config at app.routes.bindings['${externalRefId}'], '${String(\n targetRefId,\n )}' is not a valid route`,\n context: { routeId: String(targetRefId) },\n });\n continue;\n }\n\n result.set(externalRef, targetRef);\n }\n }\n }\n\n // Finally fall back to attempting to map defaults, at lowest priority\n for (const externalRef of routesById.externalRoutes.values()) {\n if (!result.has(externalRef) && !disabledExternalRefs.has(externalRef)) {\n const defaultRefId =\n OpaqueExternalRouteRef.toInternal(externalRef).getDefaultTarget();\n if (defaultRefId) {\n const defaultRef = routesById.routes.get(defaultRefId);\n if (defaultRef) {\n result.set(externalRef, defaultRef);\n }\n }\n }\n }\n\n return result;\n}\n\nfunction isValidTargetRefId(value: unknown): value is string | false {\n if (value === false) {\n return true;\n }\n\n if (typeof value === 'string' && value) {\n return true;\n }\n\n return false;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolveRouteBindings.esm.js","sources":["../../src/routing/resolveRouteBindings.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n RouteRef,\n SubRouteRef,\n ExternalRouteRef,\n} from '@backstage/frontend-plugin-api';\nimport { RouteRefsById } from './collectRouteIds';\nimport { ErrorCollector } from '../wiring/createErrorCollector';\nimport { Config } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { OpaqueExternalRouteRef } from '@internal/frontend';\n\n/**\n * Extracts a union of the keys in a map whose value extends the given type\n *\n * @ignore\n */\ntype KeysWithType<Obj extends { [key in string]: any }, Type> = {\n [key in keyof Obj]: Obj[key] extends Type ? key : never;\n}[keyof Obj];\n\n/**\n * Takes a map Map required values and makes all keys matching Keys optional\n *\n * @ignore\n */\ntype PartialKeys<\n Map extends { [name in string]: any },\n Keys extends keyof Map,\n> = Partial<Pick<Map, Keys>> & Required<Omit<Map, Keys>>;\n\n/**\n * Creates a map of target routes with matching parameters based on a map of external routes.\n *\n * @ignore\n */\ntype TargetRouteMap<\n ExternalRoutes extends { [name: string]: ExternalRouteRef },\n> = {\n [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef<\n infer Params\n >\n ? RouteRef<Params> | SubRouteRef<Params> | false\n : never;\n};\n\n/**\n * A function that can bind from external routes of a given plugin, to concrete\n * routes of other plugins. See {@link @backstage/frontend-defaults#createApp}.\n *\n * @public\n */\nexport type CreateAppRouteBinder = <\n TExternalRoutes extends { [name: string]: ExternalRouteRef },\n>(\n externalRoutes: TExternalRoutes,\n targetRoutes: PartialKeys<\n TargetRouteMap<TExternalRoutes>,\n KeysWithType<TExternalRoutes, ExternalRouteRef<any>>\n >,\n) => void;\n\n/** @internal */\nexport function resolveRouteBindings(\n bindRoutes: ((context: { bind: CreateAppRouteBinder }) => void) | undefined,\n config: Config,\n routesById: RouteRefsById,\n collector: ErrorCollector,\n): Map<ExternalRouteRef, RouteRef | SubRouteRef> {\n const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();\n const disabledExternalRefs = new Set<ExternalRouteRef>();\n\n // Perform callback bindings first with highest priority\n if (bindRoutes) {\n const bind: CreateAppRouteBinder = (\n externalRoutes,\n targetRoutes: { [name: string]: RouteRef | SubRouteRef },\n ) => {\n for (const [key, value] of Object.entries(targetRoutes)) {\n const externalRoute = externalRoutes[key];\n if (!externalRoute) {\n collector.report({\n code: 'ROUTE_NOT_FOUND',\n message: `Key ${key} is not an existing external route`,\n context: { routeId: String(key) },\n });\n continue;\n }\n if (value) {\n result.set(externalRoute, value);\n } else if (value === false) {\n disabledExternalRefs.add(externalRoute);\n }\n }\n };\n bindRoutes({ bind });\n }\n\n // Then perform config based bindings with lower priority\n const bindings = config\n .getOptionalConfig('app.routes.bindings')\n ?.get<JsonObject>();\n if (bindings) {\n for (const [externalRefId, targetRefId] of Object.entries(bindings)) {\n if (!isValidTargetRefId(targetRefId)) {\n collector.report({\n code: 'ROUTE_BINDING_INVALID_VALUE',\n message: `Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string or false`,\n context: { routeId: externalRefId },\n });\n continue;\n }\n\n const externalRef = routesById.externalRoutes.get(externalRefId);\n if (!externalRef) {\n collector.report({\n code: 'ROUTE_NOT_FOUND',\n message: `Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`,\n context: { routeId: externalRefId },\n });\n continue;\n }\n\n // Skip if binding was already defined in code\n if (result.has(externalRef) || disabledExternalRefs.has(externalRef)) {\n continue;\n }\n\n if (targetRefId === false) {\n disabledExternalRefs.add(externalRef);\n } else {\n const targetRef = routesById.routes.get(targetRefId);\n if (!targetRef) {\n collector.report({\n code: 'ROUTE_NOT_FOUND',\n message: `Invalid config at app.routes.bindings['${externalRefId}'], '${String(\n targetRefId,\n )}' is not a valid route`,\n context: { routeId: String(targetRefId) },\n });\n continue;\n }\n\n result.set(externalRef, targetRef);\n }\n }\n }\n\n // Finally fall back to attempting to map defaults, at lowest priority\n for (const externalRef of routesById.externalRoutes.values()) {\n if (!result.has(externalRef) && !disabledExternalRefs.has(externalRef)) {\n const defaultRefId =\n OpaqueExternalRouteRef.toInternal(externalRef).getDefaultTarget();\n if (defaultRefId) {\n const defaultRef = routesById.routes.get(defaultRefId);\n if (defaultRef) {\n result.set(externalRef, defaultRef);\n }\n }\n }\n }\n\n return result;\n}\n\nfunction isValidTargetRefId(value: unknown): value is string | false {\n if (value === false) {\n return true;\n }\n\n if (typeof value === 'string' && value) {\n return true;\n }\n\n return false;\n}\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import mapValues from 'lodash/mapValues';
2
2
  import { toInternalExtension } from '../frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js';
3
+ import { evaluateFilterPredicate } from '@backstage/filter-predicates';
3
4
  import { createExtensionDataContainer } from '../frontend-internal/src/wiring/createExtensionDataContainer.esm.js';
4
5
  import '../frontend-internal/src/wiring/InternalExtensionDefinition.esm.js';
5
6
  import '../frontend-internal/src/wiring/InternalExtensionInput.esm.js';
@@ -215,6 +216,18 @@ function createAppNodeInstance(options) {
215
216
  const { id, extension, config } = node.spec;
216
217
  const extensionData = /* @__PURE__ */ new Map();
217
218
  const extensionDataRefs = /* @__PURE__ */ new Set();
219
+ const scopedApis = options.onMissingApi === void 0 ? apis : {
220
+ get(apiRef) {
221
+ const api = apis.get(apiRef);
222
+ if (api === void 0) {
223
+ options.onMissingApi?.({
224
+ node,
225
+ apiRefId: apiRef.id
226
+ });
227
+ }
228
+ return api;
229
+ }
230
+ };
218
231
  let parsedConfig;
219
232
  try {
220
233
  parsedConfig = extension.configSchema?.parse(config ?? {});
@@ -233,7 +246,7 @@ function createAppNodeInstance(options) {
233
246
  if (internalExtension.version === "v1") {
234
247
  const namedOutputs = internalExtension.factory({
235
248
  node,
236
- apis,
249
+ apis: scopedApis,
237
250
  config: parsedConfig,
238
251
  inputs: resolveV1Inputs(internalExtension.inputs, attachments)
239
252
  });
@@ -253,7 +266,7 @@ function createAppNodeInstance(options) {
253
266
  } else if (internalExtension.version === "v2") {
254
267
  const context = {
255
268
  node,
256
- apis,
269
+ apis: scopedApis,
257
270
  config: parsedConfig,
258
271
  inputs: resolveV2Inputs(
259
272
  internalExtension.inputs,
@@ -349,38 +362,82 @@ function createAppNodeInstance(options) {
349
362
  }
350
363
  };
351
364
  }
352
- function instantiateAppNodeTree(rootNode, apis, collector, extensionFactoryMiddleware) {
365
+ function instantiateAppNodeSubtree(options) {
366
+ const instantiatedNodes = /* @__PURE__ */ new WeakMap();
353
367
  function createInstance(node) {
354
- if (node.instance) {
355
- return node.instance;
368
+ if (instantiatedNodes.has(node)) {
369
+ return instantiatedNodes.get(node) ?? void 0;
370
+ }
371
+ if (options.reuseExistingInstances !== false && node.instance) {
372
+ instantiatedNodes.set(node, node);
373
+ return node;
356
374
  }
357
375
  if (node.spec.disabled) {
376
+ instantiatedNodes.set(node, null);
377
+ return void 0;
378
+ }
379
+ if (options.predicateContext !== void 0 && node.spec.if !== void 0 && !evaluateFilterPredicate(node.spec.if, options.predicateContext)) {
380
+ instantiatedNodes.set(node, null);
358
381
  return void 0;
359
382
  }
360
383
  const instantiatedAttachments = /* @__PURE__ */ new Map();
361
384
  for (const [input, children] of node.edges.attachments) {
385
+ if (options.stopAtAttachment?.({ node, input })) {
386
+ continue;
387
+ }
362
388
  const instantiatedChildren = children.flatMap((child) => {
363
- const childInstance = createInstance(child);
364
- if (!childInstance) {
389
+ if (options.skipChild?.({ node, input, child })) {
365
390
  return [];
366
391
  }
367
- return [child];
392
+ const childNode = createInstance(child);
393
+ return childNode ? [childNode] : [];
368
394
  });
369
395
  if (instantiatedChildren.length > 0) {
370
396
  instantiatedAttachments.set(input, instantiatedChildren);
371
397
  }
372
398
  }
373
- node.instance = createAppNodeInstance({
374
- extensionFactoryMiddleware,
399
+ const instance = createAppNodeInstance({
400
+ extensionFactoryMiddleware: options.extensionFactoryMiddleware,
375
401
  node,
376
- apis,
402
+ apis: options.apis,
377
403
  attachments: instantiatedAttachments,
378
- collector
404
+ collector: options.collector,
405
+ onMissingApi: options.onMissingApi
379
406
  });
380
- return node.instance;
407
+ if (!instance) {
408
+ instantiatedNodes.set(node, null);
409
+ return void 0;
410
+ }
411
+ if (options.writeNodeInstances === false) {
412
+ const detachedNode = {
413
+ spec: node.spec,
414
+ edges: node.edges,
415
+ instance
416
+ };
417
+ instantiatedNodes.set(node, detachedNode);
418
+ return detachedNode;
419
+ }
420
+ node.instance = instance;
421
+ instantiatedNodes.set(node, node);
422
+ return node;
381
423
  }
382
- return createInstance(rootNode) !== void 0;
424
+ return createInstance(options.rootNode);
425
+ }
426
+ function instantiateAppNodeTree(rootNode, apis, collector, extensionFactoryMiddleware, optionsOrPredicateContext) {
427
+ const options = optionsOrPredicateContext && ("stopAtAttachment" in optionsOrPredicateContext || "skipChild" in optionsOrPredicateContext || "onMissingApi" in optionsOrPredicateContext || "predicateContext" in optionsOrPredicateContext) ? optionsOrPredicateContext : {
428
+ predicateContext: optionsOrPredicateContext
429
+ };
430
+ return instantiateAppNodeSubtree({
431
+ rootNode,
432
+ apis,
433
+ collector,
434
+ extensionFactoryMiddleware,
435
+ stopAtAttachment: options.stopAtAttachment,
436
+ skipChild: options.skipChild,
437
+ onMissingApi: options.onMissingApi,
438
+ predicateContext: options.predicateContext
439
+ }) !== void 0;
383
440
  }
384
441
 
385
- export { createAppNodeInstance, instantiateAppNodeTree };
442
+ export { createAppNodeInstance, instantiateAppNodeSubtree, instantiateAppNodeTree };
386
443
  //# sourceMappingURL=instantiateAppNodeTree.esm.js.map