@backstage/frontend-app-api 0.16.8 → 0.16.9-next.1
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.
- package/CHANGELOG.md +23 -0
- package/dist/catalog-model/src/entity/constants.esm.js.map +1 -1
- package/dist/catalog-model/src/entity/ref.esm.js.map +1 -1
- package/dist/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.esm.js.map +1 -1
- package/dist/core-app-api/src/apis/implementations/IdentityApi/startCookieAuthRefresh.esm.js.map +1 -1
- package/dist/extensions/Root.esm.js.map +1 -1
- package/dist/frontend-internal/src/apis/OpaqueApiRef.esm.js.map +1 -1
- package/dist/frontend-internal/src/routing/OpaqueExternalRouteRef.esm.js.map +1 -1
- package/dist/frontend-internal/src/routing/OpaqueRouteRef.esm.js.map +1 -1
- package/dist/frontend-internal/src/routing/OpaqueSubRouteRef.esm.js.map +1 -1
- package/dist/frontend-internal/src/wiring/InternalExtensionDefinition.esm.js.map +1 -1
- package/dist/frontend-internal/src/wiring/InternalExtensionInput.esm.js.map +1 -1
- package/dist/frontend-internal/src/wiring/InternalFrontendPlugin.esm.js.map +1 -1
- package/dist/frontend-internal/src/wiring/createExtensionDataContainer.esm.js.map +1 -1
- package/dist/frontend-plugin-api/src/wiring/createFrontendModule.esm.js.map +1 -1
- package/dist/frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js.map +1 -1
- package/dist/opaque-internal/src/OpaqueType.esm.js.map +1 -1
- package/dist/routing/RouteAliasResolver.esm.js.map +1 -1
- package/dist/routing/RouteResolver.esm.js +23 -9
- package/dist/routing/RouteResolver.esm.js.map +1 -1
- package/dist/routing/collectRouteIds.esm.js.map +1 -1
- package/dist/routing/extractRouteInfoFromAppNode.esm.js.map +1 -1
- package/dist/routing/getBasePath.esm.js.map +1 -1
- package/dist/routing/joinPaths.esm.js.map +1 -1
- package/dist/routing/resolveRouteBindings.esm.js.map +1 -1
- package/dist/tree/instantiateAppNodeTree.esm.js.map +1 -1
- package/dist/tree/readAppExtensionsConfig.esm.js.map +1 -1
- package/dist/tree/resolveAppNodeSpecs.esm.js.map +1 -1
- package/dist/tree/resolveAppTree.esm.js.map +1 -1
- package/dist/wiring/FrontendApiRegistry.esm.js.map +1 -1
- package/dist/wiring/apiFactories.esm.js.map +1 -1
- package/dist/wiring/createErrorCollector.esm.js.map +1 -1
- package/dist/wiring/createPluginInfoAttacher.esm.js.map +1 -1
- package/dist/wiring/createSpecializedApp.esm.js.map +1 -1
- package/dist/wiring/phaseApis.esm.js +22 -2
- package/dist/wiring/phaseApis.esm.js.map +1 -1
- package/dist/wiring/predicates.esm.js.map +1 -1
- package/dist/wiring/prepareSpecializedApp.esm.js +8 -5
- package/dist/wiring/prepareSpecializedApp.esm.js.map +1 -1
- package/dist/wiring/treeLifecycle.esm.js.map +1 -1
- package/package.json +15 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiFactories.esm.js","sources":["../../src/wiring/apiFactories.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 {\n ApiBlueprint,\n AnyApiFactory,\n ApiHolder,\n AppNode,\n FrontendFeature,\n featureFlagsApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { OpaqueApiRef, OpaqueFrontendPlugin } from '@internal/frontend';\nimport { instantiateAppNodeSubtree } from '../tree/instantiateAppNodeTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isInternalFrontendModule,\n toInternalFrontendModule,\n} from '../../../frontend-plugin-api/src/wiring/createFrontendModule';\nimport { ErrorCollector } from './createErrorCollector';\nimport {\n FrontendApiRegistry,\n FrontendApiResolver,\n} from './FrontendApiRegistry';\nimport { type ExtensionPredicateContext } from './predicates';\n\nexport type ApiFactoryEntry = {\n node: AppNode;\n pluginId: string;\n factory: AnyApiFactory;\n};\n\n/**\n * Registers feature flag declarations on an already prepared API holder.\n *\n * This is primarily used when bootstrap reuses APIs from a provided session\n * state rather than building a fresh registry from bootstrap-visible factories.\n */\nexport function registerFeatureFlagDeclarationsInHolder(\n apis: ApiHolder,\n features: FrontendFeature[],\n collector: ErrorCollector,\n) {\n const featureFlagApi = apis.get(featureFlagsApiRef);\n if (featureFlagApi) {\n registerFeatureFlagDeclarations(featureFlagApi, features, collector);\n }\n}\n\n/**\n * Decorates the feature flags API factory so plugin and module declarations are\n * registered whenever that API is instantiated.\n */\nexport function wrapFeatureFlagApiFactory(\n factory: AnyApiFactory,\n features: FrontendFeature[],\n collector: ErrorCollector,\n) {\n if (factory.api.id !== featureFlagsApiRef.id) {\n return factory;\n }\n\n return {\n ...factory,\n factory(deps) {\n const featureFlagApi = factory.factory(\n deps,\n ) as typeof featureFlagsApiRef.T;\n registerFeatureFlagDeclarations(featureFlagApi, features, collector);\n return featureFlagApi;\n },\n } as AnyApiFactory;\n}\n\n/**\n * Reconciles deferred API factories into the finalized API registry.\n *\n * It preserves bootstrap-frozen APIs, allows safe deferred additions, and\n * reports cases where bootstrap-visible extensions relied on APIs that only\n * became available during finalization.\n */\nexport function syncFinalApiFactories(options: {\n deferredApiNodes: Iterable<AppNode>;\n appApiRegistry: FrontendApiRegistry;\n apiResolver: FrontendApiResolver;\n collector: ErrorCollector;\n features: FrontendFeature[];\n bootstrapApiFactoryEntries: ReadonlyMap<string, ApiFactoryEntry>;\n bootstrapMissingApiAccesses: Map<string, { node: AppNode; apiRefId: string }>;\n predicateContext: ExtensionPredicateContext;\n}) {\n const finalApiEntries = collectApiFactoryEntries({\n apiNodes: options.deferredApiNodes,\n collector: options.collector,\n predicateContext: options.predicateContext,\n entries: new Map(options.bootstrapApiFactoryEntries),\n });\n // Only newly introduced or still-safe overrides are registered here. Any\n // bootstrap-materialized API remains frozen for the lifetime of the app.\n const changedEntries = Array.from(finalApiEntries.values()).filter(entry => {\n const bootstrapEntry = options.bootstrapApiFactoryEntries.get(\n entry.factory.api.id,\n );\n if (!bootstrapEntry) {\n return true;\n }\n if (bootstrapEntry.factory === entry.factory) {\n return false;\n }\n if (options.apiResolver.isMaterialized(entry.factory.api.id)) {\n options.collector.report({\n code: 'EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED',\n message:\n `Extension '${entry.node.spec.id}' tried to override API ` +\n `'${entry.factory.api.id}' after it had already been materialized during bootstrap. ` +\n 'The bootstrap implementation was kept and the deferred override was ignored.',\n context: {\n node: entry.node,\n apiRefId: entry.factory.api.id,\n bootstrapNode: bootstrapEntry.node,\n pluginId: entry.pluginId,\n bootstrapPluginId: bootstrapEntry.pluginId,\n },\n });\n return false;\n }\n return true;\n });\n const changedFactories = changedEntries.map(entry =>\n wrapFeatureFlagApiFactory(\n entry.factory,\n options.features,\n options.collector,\n ),\n );\n options.appApiRegistry.setAll(changedFactories);\n options.apiResolver.invalidate(\n changedFactories.map(factory => factory.api.id),\n );\n for (const bootstrapAccess of options.bootstrapMissingApiAccesses.values()) {\n if (\n options.bootstrapApiFactoryEntries.has(bootstrapAccess.apiRefId) ||\n !finalApiEntries.has(bootstrapAccess.apiRefId)\n ) {\n continue;\n }\n\n options.collector.report({\n code: 'EXTENSION_BOOTSTRAP_API_UNAVAILABLE',\n message:\n `Extension '${bootstrapAccess.node.spec.id}' tried to access API ` +\n `'${bootstrapAccess.apiRefId}' during bootstrap before it was available. ` +\n 'That API became available during finalization, so bootstrap-visible extensions must not depend on deferred APIs.',\n context: {\n node: bootstrapAccess.node,\n apiRefId: bootstrapAccess.apiRefId,\n },\n });\n }\n}\n\nconst EMPTY_API_HOLDER: ApiHolder = {\n get() {\n return undefined;\n },\n};\n\nfunction registerFeatureFlagDeclarations(\n featureFlagApi: typeof featureFlagsApiRef.T,\n features: FrontendFeature[],\n collector: ErrorCollector,\n) {\n for (const feature of features) {\n let pluginId: string | undefined;\n let flags: Array<{ name: string; description?: string }> | undefined;\n let source: string | undefined;\n\n if (OpaqueFrontendPlugin.isType(feature)) {\n pluginId = feature.id;\n flags = OpaqueFrontendPlugin.toInternal(feature).featureFlags;\n source = 'Plugin';\n } else if (isInternalFrontendModule(feature)) {\n pluginId = feature.pluginId;\n flags = toInternalFrontendModule(feature).featureFlags;\n source = 'Module for plugin';\n }\n\n if (pluginId && flags && source) {\n for (const flag of flags) {\n try {\n featureFlagApi.registerFlag({\n name: flag.name,\n description: flag.description,\n pluginId,\n });\n } catch (error) {\n collector.report({\n code: 'FEATURE_FLAG_INVALID',\n message: `${source} '${pluginId}' declared invalid feature flag '${flag.name}': ${error}`,\n context: { pluginId, flagName: flag.name, error: error as Error },\n });\n }\n }\n }\n }\n}\n\n/**\n * Instantiates API extension subtrees in isolation and extracts the factories\n * they provide without mutating the live app tree.\n *\n * The collected entries are later used both for bootstrap registration and for\n * the finalization-time reconciliation of deferred API roots.\n */\nexport function collectApiFactoryEntries(options: {\n apiNodes: Iterable<AppNode>;\n collector: ErrorCollector;\n predicateContext?: ExtensionPredicateContext;\n entries?: Map<string, ApiFactoryEntry>;\n}): Map<string, ApiFactoryEntry> {\n const factoriesById = options.entries ?? new Map<string, ApiFactoryEntry>();\n for (const apiNode of options.apiNodes) {\n // API extensions are instantiated in isolation so we can inspect the\n // produced factories without mutating the live app tree.\n const detachedApiNode = instantiateAppNodeSubtree({\n rootNode: apiNode,\n apis: EMPTY_API_HOLDER,\n collector: options.collector,\n predicateContext: options.predicateContext,\n writeNodeInstances: false,\n reuseExistingInstances: false,\n });\n if (!detachedApiNode) {\n continue;\n }\n const apiFactory = detachedApiNode.instance?.getData(\n ApiBlueprint.dataRefs.factory,\n );\n if (apiFactory) {\n const apiRefId = apiFactory.api.id;\n const ownerId = getApiOwnerId(apiFactory.api);\n const pluginId = apiNode.spec.plugin.pluginId ?? 'app';\n const existingFactory = factoriesById.get(apiRefId);\n\n // This allows modules to override factories provided by the plugin, but\n // it rejects API overrides from other plugins. In the event of a\n // conflict, the owning plugin is inferred from explicit ownership\n // metadata or the API reference ID.\n if (existingFactory && existingFactory.pluginId !== pluginId) {\n const shouldReplace =\n ownerId === pluginId && existingFactory.pluginId !== ownerId;\n const acceptedPluginId = shouldReplace\n ? pluginId\n : existingFactory.pluginId;\n const rejectedPluginId = shouldReplace\n ? existingFactory.pluginId\n : pluginId;\n\n options.collector.report({\n code: 'API_FACTORY_CONFLICT',\n message: `API '${apiRefId}' is already provided by plugin '${acceptedPluginId}', cannot also be provided by '${rejectedPluginId}'.`,\n context: {\n node: apiNode,\n apiRefId,\n pluginId: rejectedPluginId,\n existingPluginId: acceptedPluginId,\n },\n });\n if (shouldReplace) {\n factoriesById.set(apiRefId, {\n pluginId,\n node: apiNode,\n factory: apiFactory,\n });\n }\n continue;\n }\n\n factoriesById.set(apiRefId, {\n pluginId,\n node: apiNode,\n factory: apiFactory,\n });\n } else {\n options.collector.report({\n code: 'API_EXTENSION_INVALID',\n message: `API extension '${apiNode.spec.id}' did not output an API factory`,\n context: {\n node: apiNode,\n },\n });\n }\n }\n\n return factoriesById;\n}\n\n// TODO(Rugvip): It would be good if this was more explicit, but I think that\n// might need to wait for some future update for API factories.\nfunction getApiOwnerId(apiRef: { id: string }): string {\n if (OpaqueApiRef.isType(apiRef)) {\n const { pluginId } = OpaqueApiRef.toInternal(apiRef);\n if (pluginId) {\n return pluginId;\n }\n }\n\n const apiRefId = apiRef.id;\n const [prefix, ...rest] = apiRefId.split('.');\n if (!prefix) {\n return apiRefId;\n }\n if (prefix === 'core') {\n return 'app';\n }\n if (prefix === 'plugin' && rest[0]) {\n return rest[0];\n }\n return prefix;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"apiFactories.esm.js","sources":["../../src/wiring/apiFactories.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 {\n ApiBlueprint,\n AnyApiFactory,\n ApiHolder,\n AppNode,\n FrontendFeature,\n featureFlagsApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { OpaqueApiRef, OpaqueFrontendPlugin } from '@internal/frontend';\nimport { instantiateAppNodeSubtree } from '../tree/instantiateAppNodeTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isInternalFrontendModule,\n toInternalFrontendModule,\n} from '../../../frontend-plugin-api/src/wiring/createFrontendModule';\nimport { ErrorCollector } from './createErrorCollector';\nimport {\n FrontendApiRegistry,\n FrontendApiResolver,\n} from './FrontendApiRegistry';\nimport { type ExtensionPredicateContext } from './predicates';\n\nexport type ApiFactoryEntry = {\n node: AppNode;\n pluginId: string;\n factory: AnyApiFactory;\n};\n\n/**\n * Registers feature flag declarations on an already prepared API holder.\n *\n * This is primarily used when bootstrap reuses APIs from a provided session\n * state rather than building a fresh registry from bootstrap-visible factories.\n */\nexport function registerFeatureFlagDeclarationsInHolder(\n apis: ApiHolder,\n features: FrontendFeature[],\n collector: ErrorCollector,\n) {\n const featureFlagApi = apis.get(featureFlagsApiRef);\n if (featureFlagApi) {\n registerFeatureFlagDeclarations(featureFlagApi, features, collector);\n }\n}\n\n/**\n * Decorates the feature flags API factory so plugin and module declarations are\n * registered whenever that API is instantiated.\n */\nexport function wrapFeatureFlagApiFactory(\n factory: AnyApiFactory,\n features: FrontendFeature[],\n collector: ErrorCollector,\n) {\n if (factory.api.id !== featureFlagsApiRef.id) {\n return factory;\n }\n\n return {\n ...factory,\n factory(deps) {\n const featureFlagApi = factory.factory(\n deps,\n ) as typeof featureFlagsApiRef.T;\n registerFeatureFlagDeclarations(featureFlagApi, features, collector);\n return featureFlagApi;\n },\n } as AnyApiFactory;\n}\n\n/**\n * Reconciles deferred API factories into the finalized API registry.\n *\n * It preserves bootstrap-frozen APIs, allows safe deferred additions, and\n * reports cases where bootstrap-visible extensions relied on APIs that only\n * became available during finalization.\n */\nexport function syncFinalApiFactories(options: {\n deferredApiNodes: Iterable<AppNode>;\n appApiRegistry: FrontendApiRegistry;\n apiResolver: FrontendApiResolver;\n collector: ErrorCollector;\n features: FrontendFeature[];\n bootstrapApiFactoryEntries: ReadonlyMap<string, ApiFactoryEntry>;\n bootstrapMissingApiAccesses: Map<string, { node: AppNode; apiRefId: string }>;\n predicateContext: ExtensionPredicateContext;\n}) {\n const finalApiEntries = collectApiFactoryEntries({\n apiNodes: options.deferredApiNodes,\n collector: options.collector,\n predicateContext: options.predicateContext,\n entries: new Map(options.bootstrapApiFactoryEntries),\n });\n // Only newly introduced or still-safe overrides are registered here. Any\n // bootstrap-materialized API remains frozen for the lifetime of the app.\n const changedEntries = Array.from(finalApiEntries.values()).filter(entry => {\n const bootstrapEntry = options.bootstrapApiFactoryEntries.get(\n entry.factory.api.id,\n );\n if (!bootstrapEntry) {\n return true;\n }\n if (bootstrapEntry.factory === entry.factory) {\n return false;\n }\n if (options.apiResolver.isMaterialized(entry.factory.api.id)) {\n options.collector.report({\n code: 'EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED',\n message:\n `Extension '${entry.node.spec.id}' tried to override API ` +\n `'${entry.factory.api.id}' after it had already been materialized during bootstrap. ` +\n 'The bootstrap implementation was kept and the deferred override was ignored.',\n context: {\n node: entry.node,\n apiRefId: entry.factory.api.id,\n bootstrapNode: bootstrapEntry.node,\n pluginId: entry.pluginId,\n bootstrapPluginId: bootstrapEntry.pluginId,\n },\n });\n return false;\n }\n return true;\n });\n const changedFactories = changedEntries.map(entry =>\n wrapFeatureFlagApiFactory(\n entry.factory,\n options.features,\n options.collector,\n ),\n );\n options.appApiRegistry.setAll(changedFactories);\n options.apiResolver.invalidate(\n changedFactories.map(factory => factory.api.id),\n );\n for (const bootstrapAccess of options.bootstrapMissingApiAccesses.values()) {\n if (\n options.bootstrapApiFactoryEntries.has(bootstrapAccess.apiRefId) ||\n !finalApiEntries.has(bootstrapAccess.apiRefId)\n ) {\n continue;\n }\n\n options.collector.report({\n code: 'EXTENSION_BOOTSTRAP_API_UNAVAILABLE',\n message:\n `Extension '${bootstrapAccess.node.spec.id}' tried to access API ` +\n `'${bootstrapAccess.apiRefId}' during bootstrap before it was available. ` +\n 'That API became available during finalization, so bootstrap-visible extensions must not depend on deferred APIs.',\n context: {\n node: bootstrapAccess.node,\n apiRefId: bootstrapAccess.apiRefId,\n },\n });\n }\n}\n\nconst EMPTY_API_HOLDER: ApiHolder = {\n get() {\n return undefined;\n },\n};\n\nfunction registerFeatureFlagDeclarations(\n featureFlagApi: typeof featureFlagsApiRef.T,\n features: FrontendFeature[],\n collector: ErrorCollector,\n) {\n for (const feature of features) {\n let pluginId: string | undefined;\n let flags: Array<{ name: string; description?: string }> | undefined;\n let source: string | undefined;\n\n if (OpaqueFrontendPlugin.isType(feature)) {\n pluginId = feature.id;\n flags = OpaqueFrontendPlugin.toInternal(feature).featureFlags;\n source = 'Plugin';\n } else if (isInternalFrontendModule(feature)) {\n pluginId = feature.pluginId;\n flags = toInternalFrontendModule(feature).featureFlags;\n source = 'Module for plugin';\n }\n\n if (pluginId && flags && source) {\n for (const flag of flags) {\n try {\n featureFlagApi.registerFlag({\n name: flag.name,\n description: flag.description,\n pluginId,\n });\n } catch (error) {\n collector.report({\n code: 'FEATURE_FLAG_INVALID',\n message: `${source} '${pluginId}' declared invalid feature flag '${flag.name}': ${error}`,\n context: { pluginId, flagName: flag.name, error: error as Error },\n });\n }\n }\n }\n }\n}\n\n/**\n * Instantiates API extension subtrees in isolation and extracts the factories\n * they provide without mutating the live app tree.\n *\n * The collected entries are later used both for bootstrap registration and for\n * the finalization-time reconciliation of deferred API roots.\n */\nexport function collectApiFactoryEntries(options: {\n apiNodes: Iterable<AppNode>;\n collector: ErrorCollector;\n predicateContext?: ExtensionPredicateContext;\n entries?: Map<string, ApiFactoryEntry>;\n}): Map<string, ApiFactoryEntry> {\n const factoriesById = options.entries ?? new Map<string, ApiFactoryEntry>();\n for (const apiNode of options.apiNodes) {\n // API extensions are instantiated in isolation so we can inspect the\n // produced factories without mutating the live app tree.\n const detachedApiNode = instantiateAppNodeSubtree({\n rootNode: apiNode,\n apis: EMPTY_API_HOLDER,\n collector: options.collector,\n predicateContext: options.predicateContext,\n writeNodeInstances: false,\n reuseExistingInstances: false,\n });\n if (!detachedApiNode) {\n continue;\n }\n const apiFactory = detachedApiNode.instance?.getData(\n ApiBlueprint.dataRefs.factory,\n );\n if (apiFactory) {\n const apiRefId = apiFactory.api.id;\n const ownerId = getApiOwnerId(apiFactory.api);\n const pluginId = apiNode.spec.plugin.pluginId ?? 'app';\n const existingFactory = factoriesById.get(apiRefId);\n\n // This allows modules to override factories provided by the plugin, but\n // it rejects API overrides from other plugins. In the event of a\n // conflict, the owning plugin is inferred from explicit ownership\n // metadata or the API reference ID.\n if (existingFactory && existingFactory.pluginId !== pluginId) {\n const shouldReplace =\n ownerId === pluginId && existingFactory.pluginId !== ownerId;\n const acceptedPluginId = shouldReplace\n ? pluginId\n : existingFactory.pluginId;\n const rejectedPluginId = shouldReplace\n ? existingFactory.pluginId\n : pluginId;\n\n options.collector.report({\n code: 'API_FACTORY_CONFLICT',\n message: `API '${apiRefId}' is already provided by plugin '${acceptedPluginId}', cannot also be provided by '${rejectedPluginId}'.`,\n context: {\n node: apiNode,\n apiRefId,\n pluginId: rejectedPluginId,\n existingPluginId: acceptedPluginId,\n },\n });\n if (shouldReplace) {\n factoriesById.set(apiRefId, {\n pluginId,\n node: apiNode,\n factory: apiFactory,\n });\n }\n continue;\n }\n\n factoriesById.set(apiRefId, {\n pluginId,\n node: apiNode,\n factory: apiFactory,\n });\n } else {\n options.collector.report({\n code: 'API_EXTENSION_INVALID',\n message: `API extension '${apiNode.spec.id}' did not output an API factory`,\n context: {\n node: apiNode,\n },\n });\n }\n }\n\n return factoriesById;\n}\n\n// TODO(Rugvip): It would be good if this was more explicit, but I think that\n// might need to wait for some future update for API factories.\nfunction getApiOwnerId(apiRef: { id: string }): string {\n if (OpaqueApiRef.isType(apiRef)) {\n const { pluginId } = OpaqueApiRef.toInternal(apiRef);\n if (pluginId) {\n return pluginId;\n }\n }\n\n const apiRefId = apiRef.id;\n const [prefix, ...rest] = apiRefId.split('.');\n if (!prefix) {\n return apiRefId;\n }\n if (prefix === 'core') {\n return 'app';\n }\n if (prefix === 'plugin' && rest[0]) {\n return rest[0];\n }\n return prefix;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createErrorCollector.esm.js","sources":["../../src/wiring/createErrorCollector.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 { AppNode, FrontendPlugin } from '@backstage/frontend-plugin-api';\n\n/**\n * @public\n */\nexport type AppErrorTypes = {\n // resolveAppNodeSpecs\n EXTENSION_IGNORED: {\n context: { plugin: FrontendPlugin; extensionId: string };\n };\n INVALID_EXTENSION_CONFIG_KEY: {\n context: { extensionId: string };\n };\n // resolveAppTree\n EXTENSION_INPUT_REDIRECT_CONFLICT: {\n context: { node: AppNode; inputName: string };\n };\n // instantiateAppNodeTree\n EXTENSION_INPUT_DATA_IGNORED: {\n context: { node: AppNode; inputName: string };\n };\n EXTENSION_INPUT_DATA_MISSING: {\n context: { node: AppNode; inputName: string };\n };\n EXTENSION_INPUT_INTERNAL_IGNORED: {\n context: {\n node: AppNode;\n inputName: string;\n extensionId: string;\n plugin: FrontendPlugin;\n };\n };\n EXTENSION_ATTACHMENT_CONFLICT: {\n context: { node: AppNode; inputName: string };\n };\n EXTENSION_ATTACHMENT_MISSING: {\n context: { node: AppNode; inputName: string };\n };\n EXTENSION_CONFIGURATION_INVALID: {\n context: { node: AppNode };\n };\n EXTENSION_INVALID: {\n context: { node: AppNode };\n };\n EXTENSION_OUTPUT_CONFLICT: {\n context: { node: AppNode; dataRefId: string };\n };\n EXTENSION_OUTPUT_MISSING: {\n context: { node: AppNode; dataRefId: string };\n };\n EXTENSION_OUTPUT_IGNORED: {\n context: { node: AppNode; dataRefId: string };\n };\n EXTENSION_FACTORY_ERROR: {\n context: { node: AppNode };\n };\n // createSpecializedApp\n API_EXTENSION_INVALID: {\n context: { node: AppNode };\n };\n API_FACTORY_CONFLICT: {\n context: {\n node: AppNode;\n apiRefId: string;\n pluginId: string;\n existingPluginId: string;\n };\n };\n EXTENSION_BOOTSTRAP_PREDICATE_IGNORED: {\n context: { node: AppNode };\n };\n EXTENSION_BOOTSTRAP_API_UNAVAILABLE: {\n context: { node: AppNode; apiRefId: string };\n };\n EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED: {\n context: {\n node: AppNode;\n apiRefId: string;\n bootstrapNode: AppNode;\n pluginId: string;\n bootstrapPluginId: string;\n };\n };\n FEATURE_FLAG_INVALID: {\n context: { pluginId: string; flagName: string; error: Error };\n };\n // routing\n ROUTE_DUPLICATE: {\n context: { routeId: string };\n };\n ROUTE_BINDING_INVALID_VALUE: {\n context: { routeId: string };\n };\n ROUTE_NOT_FOUND: {\n context: { routeId: string };\n };\n};\n\n/**\n * @public\n */\nexport type AppError =\n keyof AppErrorTypes extends infer ICode extends keyof AppErrorTypes\n ? ICode extends any\n ? {\n code: ICode;\n message: string;\n context: AppErrorTypes[ICode]['context'];\n }\n : never\n : never;\n\n/** @internal */\nexport interface ErrorCollector<TContext extends {} = {}> {\n report<TCode extends keyof AppErrorTypes>(\n report: Omit<\n AppErrorTypes[TCode]['context'],\n keyof TContext\n > extends infer IContext extends {}\n ? {} extends IContext\n ? {\n code: TCode;\n message: string;\n }\n : {\n code: TCode;\n message: string;\n context: IContext;\n }\n : never,\n ): void;\n child<TAdditionalContext extends {}>(\n context: TAdditionalContext,\n ): ErrorCollector<TContext & TAdditionalContext>;\n collectErrors(): AppError[] | undefined;\n}\n\n/** @internal */\nexport function createErrorCollector(\n context?: Partial<AppError['context']>,\n): ErrorCollector {\n const errors: AppError[] = [];\n const children: ErrorCollector[] = [];\n return {\n report(report: { code: string; message: string; context?: {} }) {\n errors.push({\n ...report,\n context: { ...context, ...report.context },\n } as AppError);\n },\n collectErrors() {\n const allErrors = [\n ...errors,\n ...children.flatMap(child => child.collectErrors() ?? []),\n ];\n errors.length = 0;\n if (allErrors.length === 0) {\n return undefined;\n }\n return allErrors;\n },\n child(childContext) {\n const child = createErrorCollector({ ...context, ...childContext });\n children.push(child);\n return child as ErrorCollector<any>;\n },\n };\n}\n"],"names":[],"mappings":"AA0JO,SAAS,qBACd,OAAA,EACgB;AAChB,EAAA,MAAM,SAAqB,EAAC;AAC5B,EAAA,MAAM,WAA6B,EAAC;AACpC,EAAA,OAAO;AAAA,IACL,OAAO,MAAA,EAAyD;AAC9D,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,GAAG,MAAA;AAAA,QACH,SAAS,EAAE,GAAG,OAAA,EAAS,GAAG,OAAO,OAAA;AAAQ,OAC9B,CAAA;AAAA,IACf,CAAA;AAAA,IACA,aAAA,GAAgB;AACd,MAAA,MAAM,SAAA,GAAY;AAAA,QAChB,GAAG,MAAA;AAAA,QACH,GAAG,SAAS,OAAA,CAAQ,CAAA,KAAA,KAAS,MAAM,aAAA,EAAc,IAAK,EAAE;AAAA,OAC1D;AACA,MAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAChB,MAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,QAAA,OAAO,MAAA;AAAA,MACT;AACA,MAAA,OAAO,SAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,YAAA,EAAc;AAClB,MAAA,MAAM,QAAQ,oBAAA,CAAqB,EAAE,GAAG,OAAA,EAAS,GAAG,cAAc,CAAA;AAClE,MAAA,QAAA,CAAS,KAAK,KAAK,CAAA;AACnB,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,GACF;AACF
|
|
1
|
+
{"version":3,"file":"createErrorCollector.esm.js","sources":["../../src/wiring/createErrorCollector.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 { AppNode, FrontendPlugin } from '@backstage/frontend-plugin-api';\n\n/**\n * @public\n */\nexport type AppErrorTypes = {\n // resolveAppNodeSpecs\n EXTENSION_IGNORED: {\n context: { plugin: FrontendPlugin; extensionId: string };\n };\n INVALID_EXTENSION_CONFIG_KEY: {\n context: { extensionId: string };\n };\n // resolveAppTree\n EXTENSION_INPUT_REDIRECT_CONFLICT: {\n context: { node: AppNode; inputName: string };\n };\n // instantiateAppNodeTree\n EXTENSION_INPUT_DATA_IGNORED: {\n context: { node: AppNode; inputName: string };\n };\n EXTENSION_INPUT_DATA_MISSING: {\n context: { node: AppNode; inputName: string };\n };\n EXTENSION_INPUT_INTERNAL_IGNORED: {\n context: {\n node: AppNode;\n inputName: string;\n extensionId: string;\n plugin: FrontendPlugin;\n };\n };\n EXTENSION_ATTACHMENT_CONFLICT: {\n context: { node: AppNode; inputName: string };\n };\n EXTENSION_ATTACHMENT_MISSING: {\n context: { node: AppNode; inputName: string };\n };\n EXTENSION_CONFIGURATION_INVALID: {\n context: { node: AppNode };\n };\n EXTENSION_INVALID: {\n context: { node: AppNode };\n };\n EXTENSION_OUTPUT_CONFLICT: {\n context: { node: AppNode; dataRefId: string };\n };\n EXTENSION_OUTPUT_MISSING: {\n context: { node: AppNode; dataRefId: string };\n };\n EXTENSION_OUTPUT_IGNORED: {\n context: { node: AppNode; dataRefId: string };\n };\n EXTENSION_FACTORY_ERROR: {\n context: { node: AppNode };\n };\n // createSpecializedApp\n API_EXTENSION_INVALID: {\n context: { node: AppNode };\n };\n API_FACTORY_CONFLICT: {\n context: {\n node: AppNode;\n apiRefId: string;\n pluginId: string;\n existingPluginId: string;\n };\n };\n EXTENSION_BOOTSTRAP_PREDICATE_IGNORED: {\n context: { node: AppNode };\n };\n EXTENSION_BOOTSTRAP_API_UNAVAILABLE: {\n context: { node: AppNode; apiRefId: string };\n };\n EXTENSION_BOOTSTRAP_API_OVERRIDE_IGNORED: {\n context: {\n node: AppNode;\n apiRefId: string;\n bootstrapNode: AppNode;\n pluginId: string;\n bootstrapPluginId: string;\n };\n };\n FEATURE_FLAG_INVALID: {\n context: { pluginId: string; flagName: string; error: Error };\n };\n // routing\n ROUTE_DUPLICATE: {\n context: { routeId: string };\n };\n ROUTE_BINDING_INVALID_VALUE: {\n context: { routeId: string };\n };\n ROUTE_NOT_FOUND: {\n context: { routeId: string };\n };\n};\n\n/**\n * @public\n */\nexport type AppError =\n keyof AppErrorTypes extends infer ICode extends keyof AppErrorTypes\n ? ICode extends any\n ? {\n code: ICode;\n message: string;\n context: AppErrorTypes[ICode]['context'];\n }\n : never\n : never;\n\n/** @internal */\nexport interface ErrorCollector<TContext extends {} = {}> {\n report<TCode extends keyof AppErrorTypes>(\n report: Omit<\n AppErrorTypes[TCode]['context'],\n keyof TContext\n > extends infer IContext extends {}\n ? {} extends IContext\n ? {\n code: TCode;\n message: string;\n }\n : {\n code: TCode;\n message: string;\n context: IContext;\n }\n : never,\n ): void;\n child<TAdditionalContext extends {}>(\n context: TAdditionalContext,\n ): ErrorCollector<TContext & TAdditionalContext>;\n collectErrors(): AppError[] | undefined;\n}\n\n/** @internal */\nexport function createErrorCollector(\n context?: Partial<AppError['context']>,\n): ErrorCollector {\n const errors: AppError[] = [];\n const children: ErrorCollector[] = [];\n return {\n report(report: { code: string; message: string; context?: {} }) {\n errors.push({\n ...report,\n context: { ...context, ...report.context },\n } as AppError);\n },\n collectErrors() {\n const allErrors = [\n ...errors,\n ...children.flatMap(child => child.collectErrors() ?? []),\n ];\n errors.length = 0;\n if (allErrors.length === 0) {\n return undefined;\n }\n return allErrors;\n },\n child(childContext) {\n const child = createErrorCollector({ ...context, ...childContext });\n children.push(child);\n return child as ErrorCollector<any>;\n },\n };\n}\n"],"names":[],"mappings":"AA0JO,SAAS,qBACd,OAAA,EACgB;AAChB,EAAA,MAAM,SAAqB,EAAC;AAC5B,EAAA,MAAM,WAA6B,EAAC;AACpC,EAAA,OAAO;AAAA,IACL,OAAO,MAAA,EAAyD;AAC9D,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,GAAG,MAAA;AAAA,QACH,SAAS,EAAE,GAAG,OAAA,EAAS,GAAG,OAAO,OAAA;AAAQ,OAC9B,CAAA;AAAA,IACf,CAAA;AAAA,IACA,aAAA,GAAgB;AACd,MAAA,MAAM,SAAA,GAAY;AAAA,QAChB,GAAG,MAAA;AAAA,QACH,GAAG,SAAS,OAAA,CAAQ,CAAA,KAAA,KAAS,MAAM,aAAA,EAAc,IAAK,EAAE;AAAA,OAC1D;AACA,MAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAChB,MAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,QAAA,OAAO,MAAA;AAAA,MACT;AACA,MAAA,OAAO,SAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,YAAA,EAAc;AAClB,MAAA,MAAM,QAAQ,oBAAA,CAAqB,EAAE,GAAG,OAAA,EAAS,GAAG,cAAc,CAAA;AAClE,MAAA,QAAA,CAAS,KAAK,KAAK,CAAA;AACnB,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,GACF;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createPluginInfoAttacher.esm.js","sources":["../../src/wiring/createPluginInfoAttacher.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 { ConfigApi } from '@backstage/frontend-plugin-api';\nimport {\n FrontendFeature,\n FrontendPluginInfo,\n} from '@backstage/frontend-plugin-api';\nimport { OpaqueFrontendPlugin } from '@internal/frontend';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport once from 'lodash/once';\n// Avoid full dependency on catalog-model\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n parseEntityRef,\n stringifyEntityRef,\n} from '../../../catalog-model/src/entity/ref';\n\n/**\n * A function that resolves plugin info from a plugin manifest and package.json.\n *\n * @public\n */\nexport type FrontendPluginInfoResolver = (ctx: {\n packageJson(): Promise<JsonObject | undefined>;\n manifest(): Promise<JsonObject | undefined>;\n defaultResolver(sources: {\n packageJson: JsonObject | undefined;\n manifest: JsonObject | undefined;\n }): Promise<{ info: FrontendPluginInfo }>;\n}) => Promise<{ info: FrontendPluginInfo }>;\n\nexport function createPluginInfoAttacher(\n config: ConfigApi,\n infoResolver: FrontendPluginInfoResolver = async ctx =>\n ctx.defaultResolver({\n packageJson: await ctx.packageJson(),\n manifest: await ctx.manifest(),\n }),\n): (feature: FrontendFeature) => FrontendFeature {\n const applyInfoOverrides = createPluginInfoOverrider(config);\n\n return (feature: FrontendFeature) => {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n return feature;\n }\n\n const plugin = OpaqueFrontendPlugin.toInternal(feature);\n\n return {\n ...plugin,\n info: once(async () => {\n const manifestLoader = plugin.infoOptions?.manifest;\n const packageJsonLoader = plugin.infoOptions?.packageJson;\n\n const { info: resolvedInfo } = await infoResolver({\n manifest: async () => manifestLoader?.(),\n packageJson: async () => packageJsonLoader?.(),\n defaultResolver: async sources => ({\n info: {\n ...resolvePackageInfo(sources.packageJson),\n ...resolveManifestInfo(sources.manifest),\n },\n }),\n });\n\n const infoWithOverrides = applyInfoOverrides(\n plugin.pluginId,\n resolvedInfo,\n );\n return normalizePluginInfo(infoWithOverrides);\n }),\n };\n };\n}\n\nfunction normalizePluginInfo(info: FrontendPluginInfo) {\n return {\n ...info,\n ownerEntityRefs: info.ownerEntityRefs?.map(ref =>\n stringifyEntityRef(\n parseEntityRef(ref, {\n defaultKind: 'Group',\n }),\n ),\n ),\n };\n}\n\nfunction createPluginInfoOverrider(config: ConfigApi) {\n const overrideConfigs =\n config.getOptionalConfigArray('app.pluginOverrides') ?? [];\n\n const overrideMatchers = overrideConfigs.map(overrideConfig => {\n const pluginIdMatcher = makeStringMatcher(\n overrideConfig.getOptionalString('match.pluginId'),\n );\n const packageNameMatcher = makeStringMatcher(\n overrideConfig.getOptionalString('match.packageName'),\n );\n const description = overrideConfig.getOptionalString('info.description');\n const ownerEntityRefs = overrideConfig.getOptionalStringArray(\n 'info.ownerEntityRefs',\n );\n const links = overrideConfig\n .getOptionalConfigArray('info.links')\n ?.map(linkConfig => ({\n title: linkConfig.getString('title'),\n url: linkConfig.getString('url'),\n }));\n\n return {\n test(pluginId: string, packageName?: string) {\n return packageNameMatcher(packageName) && pluginIdMatcher(pluginId);\n },\n info: {\n description,\n ownerEntityRefs,\n links,\n },\n };\n });\n\n return (pluginId: string, info: FrontendPluginInfo) => {\n const { packageName } = info;\n for (const matcher of overrideMatchers) {\n if (matcher.test(pluginId, packageName)) {\n if (matcher.info.description) {\n info.description = matcher.info.description;\n }\n if (matcher.info.ownerEntityRefs) {\n info.ownerEntityRefs = matcher.info.ownerEntityRefs;\n }\n if (matcher.info.links) {\n info.links = matcher.info.links;\n }\n }\n }\n return info;\n };\n}\n\nfunction resolveManifestInfo(manifest?: JsonValue) {\n if (!isJsonObject(manifest) || !isJsonObject(manifest.metadata)) {\n return undefined;\n }\n\n const info: FrontendPluginInfo = {};\n\n if (isJsonObject(manifest.spec) && typeof manifest.spec.owner === 'string') {\n info.ownerEntityRefs = [\n stringifyEntityRef(\n parseEntityRef(manifest.spec.owner, {\n defaultKind: 'Group',\n defaultNamespace: manifest.metadata.namespace?.toString(),\n }),\n ),\n ];\n }\n\n if (Array.isArray(manifest.metadata.links)) {\n info.links = manifest.metadata.links.filter(isJsonObject).map(link => ({\n title: String(link.title),\n url: String(link.url),\n }));\n }\n\n return info;\n}\n\nfunction resolvePackageInfo(packageJson?: JsonObject) {\n if (!packageJson) {\n return undefined;\n }\n\n const info: FrontendPluginInfo = {\n packageName: packageJson?.name?.toString(),\n version: packageJson?.version?.toString(),\n description: packageJson?.description?.toString(),\n };\n\n const links: { title: string; url: string }[] = [];\n\n if (typeof packageJson.homepage === 'string') {\n links.push({\n title: 'Homepage',\n url: packageJson.homepage,\n });\n }\n\n if (\n isJsonObject(packageJson.repository) &&\n typeof packageJson.repository?.url === 'string'\n ) {\n try {\n const url = new URL(packageJson.repository?.url);\n if (url.protocol === 'http:' || url.protocol === 'https:') {\n // TODO(Rugvip): Support more variants\n if (\n url.hostname === 'github.com' &&\n typeof packageJson.repository.directory === 'string'\n ) {\n const path = `${url.pathname}/tree/-/${packageJson.repository.directory}`;\n url.pathname = path.replaceAll('//', '/');\n }\n\n links.push({\n title: 'Repository',\n url: url.toString(),\n });\n }\n } catch {\n /* ignored */\n }\n }\n\n if (links.length > 0) {\n info.links = links;\n }\n return info;\n}\n\nfunction makeStringMatcher(pattern: string | undefined) {\n if (!pattern) {\n return () => true;\n }\n if (pattern.startsWith('/') && pattern.endsWith('/') && pattern.length > 2) {\n const regex = new RegExp(pattern.slice(1, -1));\n return (str?: string) => (str ? regex.test(str) : false);\n }\n\n return (str?: string) => str === pattern;\n}\n\nfunction isJsonObject(value?: JsonValue): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createPluginInfoAttacher.esm.js","sources":["../../src/wiring/createPluginInfoAttacher.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 { ConfigApi } from '@backstage/frontend-plugin-api';\nimport {\n FrontendFeature,\n FrontendPluginInfo,\n} from '@backstage/frontend-plugin-api';\nimport { OpaqueFrontendPlugin } from '@internal/frontend';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport once from 'lodash/once';\n// Avoid full dependency on catalog-model\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n parseEntityRef,\n stringifyEntityRef,\n} from '../../../catalog-model/src/entity/ref';\n\n/**\n * A function that resolves plugin info from a plugin manifest and package.json.\n *\n * @public\n */\nexport type FrontendPluginInfoResolver = (ctx: {\n packageJson(): Promise<JsonObject | undefined>;\n manifest(): Promise<JsonObject | undefined>;\n defaultResolver(sources: {\n packageJson: JsonObject | undefined;\n manifest: JsonObject | undefined;\n }): Promise<{ info: FrontendPluginInfo }>;\n}) => Promise<{ info: FrontendPluginInfo }>;\n\nexport function createPluginInfoAttacher(\n config: ConfigApi,\n infoResolver: FrontendPluginInfoResolver = async ctx =>\n ctx.defaultResolver({\n packageJson: await ctx.packageJson(),\n manifest: await ctx.manifest(),\n }),\n): (feature: FrontendFeature) => FrontendFeature {\n const applyInfoOverrides = createPluginInfoOverrider(config);\n\n return (feature: FrontendFeature) => {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n return feature;\n }\n\n const plugin = OpaqueFrontendPlugin.toInternal(feature);\n\n return {\n ...plugin,\n info: once(async () => {\n const manifestLoader = plugin.infoOptions?.manifest;\n const packageJsonLoader = plugin.infoOptions?.packageJson;\n\n const { info: resolvedInfo } = await infoResolver({\n manifest: async () => manifestLoader?.(),\n packageJson: async () => packageJsonLoader?.(),\n defaultResolver: async sources => ({\n info: {\n ...resolvePackageInfo(sources.packageJson),\n ...resolveManifestInfo(sources.manifest),\n },\n }),\n });\n\n const infoWithOverrides = applyInfoOverrides(\n plugin.pluginId,\n resolvedInfo,\n );\n return normalizePluginInfo(infoWithOverrides);\n }),\n };\n };\n}\n\nfunction normalizePluginInfo(info: FrontendPluginInfo) {\n return {\n ...info,\n ownerEntityRefs: info.ownerEntityRefs?.map(ref =>\n stringifyEntityRef(\n parseEntityRef(ref, {\n defaultKind: 'Group',\n }),\n ),\n ),\n };\n}\n\nfunction createPluginInfoOverrider(config: ConfigApi) {\n const overrideConfigs =\n config.getOptionalConfigArray('app.pluginOverrides') ?? [];\n\n const overrideMatchers = overrideConfigs.map(overrideConfig => {\n const pluginIdMatcher = makeStringMatcher(\n overrideConfig.getOptionalString('match.pluginId'),\n );\n const packageNameMatcher = makeStringMatcher(\n overrideConfig.getOptionalString('match.packageName'),\n );\n const description = overrideConfig.getOptionalString('info.description');\n const ownerEntityRefs = overrideConfig.getOptionalStringArray(\n 'info.ownerEntityRefs',\n );\n const links = overrideConfig\n .getOptionalConfigArray('info.links')\n ?.map(linkConfig => ({\n title: linkConfig.getString('title'),\n url: linkConfig.getString('url'),\n }));\n\n return {\n test(pluginId: string, packageName?: string) {\n return packageNameMatcher(packageName) && pluginIdMatcher(pluginId);\n },\n info: {\n description,\n ownerEntityRefs,\n links,\n },\n };\n });\n\n return (pluginId: string, info: FrontendPluginInfo) => {\n const { packageName } = info;\n for (const matcher of overrideMatchers) {\n if (matcher.test(pluginId, packageName)) {\n if (matcher.info.description) {\n info.description = matcher.info.description;\n }\n if (matcher.info.ownerEntityRefs) {\n info.ownerEntityRefs = matcher.info.ownerEntityRefs;\n }\n if (matcher.info.links) {\n info.links = matcher.info.links;\n }\n }\n }\n return info;\n };\n}\n\nfunction resolveManifestInfo(manifest?: JsonValue) {\n if (!isJsonObject(manifest) || !isJsonObject(manifest.metadata)) {\n return undefined;\n }\n\n const info: FrontendPluginInfo = {};\n\n if (isJsonObject(manifest.spec) && typeof manifest.spec.owner === 'string') {\n info.ownerEntityRefs = [\n stringifyEntityRef(\n parseEntityRef(manifest.spec.owner, {\n defaultKind: 'Group',\n defaultNamespace: manifest.metadata.namespace?.toString(),\n }),\n ),\n ];\n }\n\n if (Array.isArray(manifest.metadata.links)) {\n info.links = manifest.metadata.links.filter(isJsonObject).map(link => ({\n title: String(link.title),\n url: String(link.url),\n }));\n }\n\n return info;\n}\n\nfunction resolvePackageInfo(packageJson?: JsonObject) {\n if (!packageJson) {\n return undefined;\n }\n\n const info: FrontendPluginInfo = {\n packageName: packageJson?.name?.toString(),\n version: packageJson?.version?.toString(),\n description: packageJson?.description?.toString(),\n };\n\n const links: { title: string; url: string }[] = [];\n\n if (typeof packageJson.homepage === 'string') {\n links.push({\n title: 'Homepage',\n url: packageJson.homepage,\n });\n }\n\n if (\n isJsonObject(packageJson.repository) &&\n typeof packageJson.repository?.url === 'string'\n ) {\n try {\n const url = new URL(packageJson.repository?.url);\n if (url.protocol === 'http:' || url.protocol === 'https:') {\n // TODO(Rugvip): Support more variants\n if (\n url.hostname === 'github.com' &&\n typeof packageJson.repository.directory === 'string'\n ) {\n const path = `${url.pathname}/tree/-/${packageJson.repository.directory}`;\n url.pathname = path.replaceAll('//', '/');\n }\n\n links.push({\n title: 'Repository',\n url: url.toString(),\n });\n }\n } catch {\n /* ignored */\n }\n }\n\n if (links.length > 0) {\n info.links = links;\n }\n return info;\n}\n\nfunction makeStringMatcher(pattern: string | undefined) {\n if (!pattern) {\n return () => true;\n }\n if (pattern.startsWith('/') && pattern.endsWith('/') && pattern.length > 2) {\n const regex = new RegExp(pattern.slice(1, -1));\n return (str?: string) => (str ? regex.test(str) : false);\n }\n\n return (str?: string) => str === pattern;\n}\n\nfunction isJsonObject(value?: JsonValue): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSpecializedApp.esm.js","sources":["../../src/wiring/createSpecializedApp.tsx"],"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 {\n ApiHolder,\n ConfigApi,\n ExtensionFactoryMiddleware,\n FrontendFeature,\n} from '@backstage/frontend-plugin-api';\nimport { CreateAppRouteBinder } from '../routing';\nimport { FrontendPluginInfoResolver } from './createPluginInfoAttacher';\nimport {\n createSessionStateFromApis,\n CreateSpecializedAppInternalOptions,\n FinalizedSpecializedApp,\n prepareSpecializedApp,\n} from './prepareSpecializedApp';\n\nexport type { CreateSpecializedAppInternalOptions };\n\n/**\n * Options for {@link createSpecializedApp}.\n *\n * @deprecated Use `PrepareSpecializedAppOptions` with `prepareSpecializedApp` instead.\n *\n * @public\n */\nexport type CreateSpecializedAppOptions = {\n /**\n * The list of features to load.\n */\n features?: FrontendFeature[];\n\n /**\n * The config API implementation to use. For most normal apps, this should be\n * specified.\n *\n * If none is given, a new _empty_ config will be used during startup. In\n * later stages of the app lifecycle, the config API in the API holder will be\n * used.\n */\n config?: ConfigApi;\n\n /**\n * Allows for the binding of plugins' external route refs within the app.\n */\n bindRoutes?(context: { bind: CreateAppRouteBinder }): void;\n\n /**\n * Advanced, more rarely used options.\n */\n advanced?: {\n /**\n * APIs to expose to the app during startup.\n */\n apis?: ApiHolder;\n\n /**\n * Applies one or more middleware on every extension, as they are added to\n * the application.\n *\n * This is an advanced use case for modifying extension data on the fly as\n * it gets emitted by extensions being instantiated.\n */\n extensionFactoryMiddleware?:\n | ExtensionFactoryMiddleware\n | ExtensionFactoryMiddleware[];\n\n /**\n * Allows for customizing how plugin info is retrieved.\n */\n pluginInfoResolver?: FrontendPluginInfoResolver;\n };\n};\n\n/**\n * Creates an empty app without any default features. This is a low-level API is\n * intended for use in tests or specialized setups. Typically you want to use\n * `createApp` from `@backstage/frontend-defaults` instead.\n *\n * @deprecated Use `prepareSpecializedApp` instead.\n *\n * @public\n */\nexport function createSpecializedApp(\n options?: CreateSpecializedAppOptions,\n): FinalizedSpecializedApp {\n const sessionState = options?.advanced?.apis\n ? createSessionStateFromApis(options.advanced.apis)\n : undefined;\n\n return prepareSpecializedApp({\n features: options?.features,\n config: options?.config,\n bindRoutes: options?.bindRoutes,\n advanced: {\n ...options?.advanced,\n sessionState,\n },\n }).finalize();\n}\n"],"names":[],"mappings":";;AAiGO,SAAS,qBACd,OAAA,EACyB;AACzB,EAAA,MAAM,YAAA,GAAe,SAAS,QAAA,EAAU,IAAA,GACpC,2BAA2B,OAAA,CAAQ,QAAA,CAAS,IAAI,CAAA,GAChD,MAAA;AAEJ,EAAA,OAAO,qBAAA,CAAsB;AAAA,IAC3B,UAAU,OAAA,EAAS,QAAA;AAAA,IACnB,QAAQ,OAAA,EAAS,MAAA;AAAA,IACjB,YAAY,OAAA,EAAS,UAAA;AAAA,IACrB,QAAA,EAAU;AAAA,MACR,GAAG,OAAA,EAAS,QAAA;AAAA,MACZ;AAAA;AACF,GACD,EAAE,QAAA,EAAS;AACd
|
|
1
|
+
{"version":3,"file":"createSpecializedApp.esm.js","sources":["../../src/wiring/createSpecializedApp.tsx"],"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 {\n ApiHolder,\n ConfigApi,\n ExtensionFactoryMiddleware,\n FrontendFeature,\n} from '@backstage/frontend-plugin-api';\nimport { CreateAppRouteBinder } from '../routing';\nimport { FrontendPluginInfoResolver } from './createPluginInfoAttacher';\nimport {\n createSessionStateFromApis,\n CreateSpecializedAppInternalOptions,\n FinalizedSpecializedApp,\n prepareSpecializedApp,\n} from './prepareSpecializedApp';\n\nexport type { CreateSpecializedAppInternalOptions };\n\n/**\n * Options for {@link createSpecializedApp}.\n *\n * @deprecated Use `PrepareSpecializedAppOptions` with `prepareSpecializedApp` instead.\n *\n * @public\n */\nexport type CreateSpecializedAppOptions = {\n /**\n * The list of features to load.\n */\n features?: FrontendFeature[];\n\n /**\n * The config API implementation to use. For most normal apps, this should be\n * specified.\n *\n * If none is given, a new _empty_ config will be used during startup. In\n * later stages of the app lifecycle, the config API in the API holder will be\n * used.\n */\n config?: ConfigApi;\n\n /**\n * Allows for the binding of plugins' external route refs within the app.\n */\n bindRoutes?(context: { bind: CreateAppRouteBinder }): void;\n\n /**\n * Advanced, more rarely used options.\n */\n advanced?: {\n /**\n * APIs to expose to the app during startup.\n */\n apis?: ApiHolder;\n\n /**\n * Applies one or more middleware on every extension, as they are added to\n * the application.\n *\n * This is an advanced use case for modifying extension data on the fly as\n * it gets emitted by extensions being instantiated.\n */\n extensionFactoryMiddleware?:\n | ExtensionFactoryMiddleware\n | ExtensionFactoryMiddleware[];\n\n /**\n * Allows for customizing how plugin info is retrieved.\n */\n pluginInfoResolver?: FrontendPluginInfoResolver;\n };\n};\n\n/**\n * Creates an empty app without any default features. This is a low-level API is\n * intended for use in tests or specialized setups. Typically you want to use\n * `createApp` from `@backstage/frontend-defaults` instead.\n *\n * @deprecated Use `prepareSpecializedApp` instead.\n *\n * @public\n */\nexport function createSpecializedApp(\n options?: CreateSpecializedAppOptions,\n): FinalizedSpecializedApp {\n const sessionState = options?.advanced?.apis\n ? createSessionStateFromApis(options.advanced.apis)\n : undefined;\n\n return prepareSpecializedApp({\n features: options?.features,\n config: options?.config,\n bindRoutes: options?.bindRoutes,\n advanced: {\n ...options?.advanced,\n sessionState,\n },\n }).finalize();\n}\n"],"names":[],"mappings":";;AAiGO,SAAS,qBACd,OAAA,EACyB;AACzB,EAAA,MAAM,YAAA,GAAe,SAAS,QAAA,EAAU,IAAA,GACpC,2BAA2B,OAAA,CAAQ,QAAA,CAAS,IAAI,CAAA,GAChD,MAAA;AAEJ,EAAA,OAAO,qBAAA,CAAsB;AAAA,IAC3B,UAAU,OAAA,EAAS,QAAA;AAAA,IACnB,QAAQ,OAAA,EAAS,MAAA;AAAA,IACjB,YAAY,OAAA,EAAS,UAAA;AAAA,IACrB,QAAA,EAAU;AAAA,MACR,GAAG,OAAA,EAAS,QAAA;AAAA,MACZ;AAAA;AACF,GACD,EAAE,QAAA,EAAS;AACd;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createApiFactory, appTreeApiRef, configApiRef, routeResolutionApiRef, identityApiRef } from '@backstage/frontend-plugin-api';
|
|
1
|
+
import { createApiFactory, appTreeApiRef, appLifecycleApiRef, configApiRef, routeResolutionApiRef, identityApiRef } from '@backstage/frontend-plugin-api';
|
|
2
2
|
import { matchRoutes } from 'react-router-dom';
|
|
3
3
|
import { AppIdentityProxy } from '../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.esm.js';
|
|
4
4
|
import { createRouteAliasResolver } from '../routing/RouteAliasResolver.esm.js';
|
|
@@ -83,6 +83,23 @@ class RouteResolutionApiProxy {
|
|
|
83
83
|
return this.#routeObjects;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
class DefaultAppLifecycleApi {
|
|
87
|
+
#finalized = false;
|
|
88
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
89
|
+
isFinalized = () => this.#finalized;
|
|
90
|
+
subscribe = (listener) => {
|
|
91
|
+
this.#listeners.add(listener);
|
|
92
|
+
return () => {
|
|
93
|
+
this.#listeners.delete(listener);
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
markFinalized() {
|
|
97
|
+
this.#finalized = true;
|
|
98
|
+
for (const listener of this.#listeners) {
|
|
99
|
+
listener();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
86
103
|
class PreparedAppIdentityProxy extends AppIdentityProxy {
|
|
87
104
|
#onTargetSet;
|
|
88
105
|
setTargetHandlers(options) {
|
|
@@ -108,9 +125,11 @@ function createPhaseApis(options) {
|
|
|
108
125
|
options.appBasePath
|
|
109
126
|
);
|
|
110
127
|
const identityProxy = new PreparedAppIdentityProxy();
|
|
128
|
+
const appLifecycleApi = new DefaultAppLifecycleApi();
|
|
111
129
|
const phaseApiRegistry = new FrontendApiRegistry();
|
|
112
130
|
phaseApiRegistry.registerAll([
|
|
113
131
|
createApiFactory(appTreeApiRef, appTreeApi),
|
|
132
|
+
createApiFactory(appLifecycleApiRef, appLifecycleApi),
|
|
114
133
|
...options.includeConfigApi ? [createApiFactory(configApiRef, options.config)] : [],
|
|
115
134
|
createApiFactory(routeResolutionApiRef, routeResolutionApi),
|
|
116
135
|
createApiFactory(identityApiRef, identityProxy),
|
|
@@ -125,6 +144,7 @@ function createPhaseApis(options) {
|
|
|
125
144
|
apis,
|
|
126
145
|
routeResolutionApi,
|
|
127
146
|
appTreeApi,
|
|
147
|
+
appLifecycleApi,
|
|
128
148
|
identityApiProxy: identityProxy
|
|
129
149
|
};
|
|
130
150
|
}
|
|
@@ -157,5 +177,5 @@ function setIdentityApiTarget(options) {
|
|
|
157
177
|
});
|
|
158
178
|
}
|
|
159
179
|
|
|
160
|
-
export { AppTreeApiProxy, PreparedAppIdentityProxy, RouteResolutionApiProxy, createPhaseApis, instantiateAndInitializePhaseTree, setIdentityApiTarget };
|
|
180
|
+
export { AppTreeApiProxy, DefaultAppLifecycleApi, PreparedAppIdentityProxy, RouteResolutionApiProxy, createPhaseApis, instantiateAndInitializePhaseTree, setIdentityApiTarget };
|
|
161
181
|
//# sourceMappingURL=phaseApis.esm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phaseApis.esm.js","sources":["../../src/wiring/phaseApis.tsx"],"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 {\n AnyApiFactory,\n ApiHolder,\n AppTree,\n AppTreeApi,\n appTreeApiRef,\n ConfigApi,\n configApiRef,\n createApiFactory,\n ExternalRouteRef,\n identityApiRef,\n RouteFunc,\n RouteRef,\n RouteResolutionApi,\n routeResolutionApiRef,\n SubRouteRef,\n type AnyRouteRefParams,\n type AppNode,\n type ExtensionFactoryMiddleware,\n type IdentityApi,\n} from '@backstage/frontend-plugin-api';\nimport { matchRoutes } from 'react-router-dom';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';\nimport { createRouteAliasResolver } from '../routing/RouteAliasResolver';\nimport { RouteResolver } from '../routing/RouteResolver';\nimport { collectRouteIds } from '../routing/collectRouteIds';\nimport {\n extractRouteInfoFromAppNode,\n type RouteInfo,\n} from '../routing/extractRouteInfoFromAppNode';\nimport { type BackstageRouteObject } from '../routing/types';\nimport { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree';\nimport {\n FrontendApiRegistry,\n FrontendApiResolver,\n} from './FrontendApiRegistry';\nimport { type ExtensionPredicateContext } from './predicates';\nimport { type ErrorCollector } from './createErrorCollector';\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nexport class AppTreeApiProxy implements AppTreeApi {\n #routeInfo?: RouteInfo;\n private readonly tree: AppTree;\n private readonly appBasePath: string;\n\n constructor(tree: AppTree, appBasePath: string) {\n this.tree = tree;\n this.appBasePath = appBasePath;\n }\n\n private checkIfInitialized() {\n if (!this.#routeInfo) {\n throw new Error(\n `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n }\n\n getTree() {\n this.checkIfInitialized();\n\n return { tree: this.tree };\n }\n\n getNodesByRoutePath(routePath: string): { nodes: AppNode[] } {\n this.checkIfInitialized();\n const routeInfo = this.#routeInfo;\n if (!routeInfo) {\n throw new Error(\n `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n\n let path = routePath;\n if (path.startsWith(this.appBasePath)) {\n path = path.slice(this.appBasePath.length);\n }\n\n const matchedRoutes = matchRoutes(routeInfo.routeObjects, path);\n\n const matchedAppNodes =\n matchedRoutes?.flatMap(routeObj => {\n const appNode = routeObj.route.appNode;\n return appNode ? [appNode] : [];\n }) || [];\n\n return { nodes: matchedAppNodes };\n }\n\n initialize(routeInfo: RouteInfo) {\n this.#routeInfo = routeInfo;\n }\n}\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nexport class RouteResolutionApiProxy implements RouteResolutionApi {\n #delegate: RouteResolutionApi | undefined;\n #routeObjects: BackstageRouteObject[] | undefined;\n\n private readonly routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>;\n private readonly appBasePath: string;\n\n constructor(\n routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>,\n appBasePath: string,\n ) {\n this.routeBindings = routeBindings;\n this.appBasePath = appBasePath;\n }\n\n resolve<TParams extends AnyRouteRefParams>(\n anyRouteRef:\n | RouteRef<TParams>\n | SubRouteRef<TParams>\n | ExternalRouteRef<TParams>,\n options?: { sourcePath?: string },\n ): RouteFunc<TParams> | undefined {\n if (!this.#delegate) {\n throw new Error(\n `You can't access the RouteResolver during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n\n return this.#delegate.resolve(anyRouteRef, options);\n }\n\n initialize(\n routeInfo: RouteInfo,\n routeRefsById: Map<string, RouteRef | SubRouteRef>,\n ) {\n this.#delegate = new RouteResolver(\n routeInfo.routePaths,\n routeInfo.routeParents,\n routeInfo.routeObjects,\n this.routeBindings,\n this.appBasePath,\n routeInfo.routeAliasResolver,\n routeRefsById,\n );\n this.#routeObjects = routeInfo.routeObjects;\n\n return routeInfo;\n }\n\n getRouteObjects() {\n return this.#routeObjects;\n }\n}\n\nexport class PreparedAppIdentityProxy extends AppIdentityProxy {\n #onTargetSet?:\n | ((identityApi: Parameters<AppIdentityProxy['setTarget']>[0]) => void)\n | undefined;\n\n setTargetHandlers(options: {\n onTargetSet?(\n identityApi: Parameters<AppIdentityProxy['setTarget']>[0],\n ): void;\n }) {\n this.#onTargetSet = options.onTargetSet;\n }\n\n clearTargetHandlers() {\n this.#onTargetSet = undefined;\n }\n\n override setTarget(\n identityApi: Parameters<AppIdentityProxy['setTarget']>[0],\n targetOptions: Parameters<AppIdentityProxy['setTarget']>[1],\n ) {\n super.setTarget(identityApi, targetOptions);\n\n const onTargetSet = this.#onTargetSet;\n if (!onTargetSet) {\n return;\n }\n\n this.clearTargetHandlers();\n onTargetSet(identityApi);\n }\n}\n\nexport function createPhaseApis(options: {\n tree: AppTree;\n config: ConfigApi;\n appApiRegistry: FrontendApiRegistry;\n fallbackApis?: ApiHolder;\n includeConfigApi: boolean;\n appBasePath: string;\n routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>;\n staticFactories: AnyApiFactory[];\n}) {\n const appTreeApi = new AppTreeApiProxy(options.tree, options.appBasePath);\n const routeResolutionApi = new RouteResolutionApiProxy(\n options.routeBindings,\n options.appBasePath,\n );\n const identityProxy = new PreparedAppIdentityProxy();\n const phaseApiRegistry = new FrontendApiRegistry();\n phaseApiRegistry.registerAll([\n createApiFactory(appTreeApiRef, appTreeApi),\n ...(options.includeConfigApi\n ? [createApiFactory(configApiRef, options.config)]\n : []),\n createApiFactory(routeResolutionApiRef, routeResolutionApi),\n createApiFactory(identityApiRef, identityProxy),\n ...options.staticFactories,\n ]);\n\n const apis = new FrontendApiResolver({\n primaryRegistry: phaseApiRegistry,\n secondaryRegistry: options.appApiRegistry,\n fallbackApis: options.fallbackApis,\n });\n\n return {\n apis,\n routeResolutionApi,\n appTreeApi,\n identityApiProxy: identityProxy,\n };\n}\n\nexport function instantiateAndInitializePhaseTree(options: {\n tree: AppTree;\n apis: ApiHolder;\n collector: ErrorCollector;\n extensionFactoryMiddleware?: ExtensionFactoryMiddleware;\n routeResolutionApi: RouteResolutionApiProxy;\n appTreeApi: AppTreeApiProxy;\n routeRefsById: ReturnType<typeof collectRouteIds>;\n skipChild?(ctx: { node: AppNode; input: string; child: AppNode }): boolean;\n onMissingApi?(ctx: { node: AppNode; apiRefId: string }): void;\n predicateContext?: ExtensionPredicateContext;\n stopAtAttachment?(ctx: { node: AppNode; input: string }): boolean;\n}) {\n instantiateAppNodeTree(\n options.tree.root,\n options.apis,\n options.collector,\n options.extensionFactoryMiddleware,\n {\n ...(options.stopAtAttachment\n ? { stopAtAttachment: options.stopAtAttachment }\n : {}),\n skipChild: options.skipChild,\n onMissingApi: options.onMissingApi,\n predicateContext: options.predicateContext,\n },\n );\n\n const routeInfo = extractRouteInfoFromAppNode(\n options.tree.root,\n createRouteAliasResolver(options.routeRefsById),\n );\n\n options.routeResolutionApi.initialize(\n routeInfo,\n options.routeRefsById.routes,\n );\n options.appTreeApi.initialize(routeInfo);\n}\n\nexport function setIdentityApiTarget(options: {\n identityApiProxy: AppIdentityProxy;\n identityApi: IdentityApi;\n signOutTargetUrl: string;\n}) {\n options.identityApiProxy.setTarget(options.identityApi, {\n signOutTargetUrl: options.signOutTargetUrl,\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"phaseApis.esm.js","sources":["../../src/wiring/phaseApis.tsx"],"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 {\n AnyApiFactory,\n ApiHolder,\n AppLifecycleApi,\n appLifecycleApiRef,\n AppTree,\n AppTreeApi,\n appTreeApiRef,\n ConfigApi,\n configApiRef,\n createApiFactory,\n ExternalRouteRef,\n identityApiRef,\n RouteFunc,\n RouteRef,\n RouteResolutionApi,\n routeResolutionApiRef,\n SubRouteRef,\n type AnyRouteRefParams,\n type AppNode,\n type ExtensionFactoryMiddleware,\n type IdentityApi,\n} from '@backstage/frontend-plugin-api';\nimport { matchRoutes } from 'react-router-dom';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';\nimport { createRouteAliasResolver } from '../routing/RouteAliasResolver';\nimport { RouteResolver } from '../routing/RouteResolver';\nimport { collectRouteIds } from '../routing/collectRouteIds';\nimport {\n extractRouteInfoFromAppNode,\n type RouteInfo,\n} from '../routing/extractRouteInfoFromAppNode';\nimport { type BackstageRouteObject } from '../routing/types';\nimport { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree';\nimport {\n FrontendApiRegistry,\n FrontendApiResolver,\n} from './FrontendApiRegistry';\nimport { type ExtensionPredicateContext } from './predicates';\nimport { type ErrorCollector } from './createErrorCollector';\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nexport class AppTreeApiProxy implements AppTreeApi {\n #routeInfo?: RouteInfo;\n private readonly tree: AppTree;\n private readonly appBasePath: string;\n\n constructor(tree: AppTree, appBasePath: string) {\n this.tree = tree;\n this.appBasePath = appBasePath;\n }\n\n private checkIfInitialized() {\n if (!this.#routeInfo) {\n throw new Error(\n `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n }\n\n getTree() {\n this.checkIfInitialized();\n\n return { tree: this.tree };\n }\n\n getNodesByRoutePath(routePath: string): { nodes: AppNode[] } {\n this.checkIfInitialized();\n const routeInfo = this.#routeInfo;\n if (!routeInfo) {\n throw new Error(\n `You can't access the AppTreeApi during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n\n let path = routePath;\n if (path.startsWith(this.appBasePath)) {\n path = path.slice(this.appBasePath.length);\n }\n\n const matchedRoutes = matchRoutes(routeInfo.routeObjects, path);\n\n const matchedAppNodes =\n matchedRoutes?.flatMap(routeObj => {\n const appNode = routeObj.route.appNode;\n return appNode ? [appNode] : [];\n }) || [];\n\n return { nodes: matchedAppNodes };\n }\n\n initialize(routeInfo: RouteInfo) {\n this.#routeInfo = routeInfo;\n }\n}\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nexport class RouteResolutionApiProxy implements RouteResolutionApi {\n #delegate: RouteResolutionApi | undefined;\n #routeObjects: BackstageRouteObject[] | undefined;\n\n private readonly routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>;\n private readonly appBasePath: string;\n\n constructor(\n routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>,\n appBasePath: string,\n ) {\n this.routeBindings = routeBindings;\n this.appBasePath = appBasePath;\n }\n\n resolve<TParams extends AnyRouteRefParams>(\n anyRouteRef:\n | RouteRef<TParams>\n | SubRouteRef<TParams>\n | ExternalRouteRef<TParams>,\n options?: { sourcePath?: string },\n ): RouteFunc<TParams> | undefined {\n if (!this.#delegate) {\n throw new Error(\n `You can't access the RouteResolver during initialization of the app tree. Please move occurrences of this out of the initialization of the factory`,\n );\n }\n\n return this.#delegate.resolve(anyRouteRef, options);\n }\n\n initialize(\n routeInfo: RouteInfo,\n routeRefsById: Map<string, RouteRef | SubRouteRef>,\n ) {\n this.#delegate = new RouteResolver(\n routeInfo.routePaths,\n routeInfo.routeParents,\n routeInfo.routeObjects,\n this.routeBindings,\n this.appBasePath,\n routeInfo.routeAliasResolver,\n routeRefsById,\n );\n this.#routeObjects = routeInfo.routeObjects;\n\n return routeInfo;\n }\n\n getRouteObjects() {\n return this.#routeObjects;\n }\n}\n\nexport class DefaultAppLifecycleApi implements AppLifecycleApi {\n #finalized = false;\n readonly #listeners = new Set<() => void>();\n\n isFinalized = () => this.#finalized;\n\n subscribe = (listener: () => void) => {\n this.#listeners.add(listener);\n\n return () => {\n this.#listeners.delete(listener);\n };\n };\n\n markFinalized() {\n this.#finalized = true;\n\n for (const listener of this.#listeners) {\n listener();\n }\n }\n}\n\nexport class PreparedAppIdentityProxy extends AppIdentityProxy {\n #onTargetSet?:\n | ((identityApi: Parameters<AppIdentityProxy['setTarget']>[0]) => void)\n | undefined;\n\n setTargetHandlers(options: {\n onTargetSet?(\n identityApi: Parameters<AppIdentityProxy['setTarget']>[0],\n ): void;\n }) {\n this.#onTargetSet = options.onTargetSet;\n }\n\n clearTargetHandlers() {\n this.#onTargetSet = undefined;\n }\n\n override setTarget(\n identityApi: Parameters<AppIdentityProxy['setTarget']>[0],\n targetOptions: Parameters<AppIdentityProxy['setTarget']>[1],\n ) {\n super.setTarget(identityApi, targetOptions);\n\n const onTargetSet = this.#onTargetSet;\n if (!onTargetSet) {\n return;\n }\n\n this.clearTargetHandlers();\n onTargetSet(identityApi);\n }\n}\n\nexport function createPhaseApis(options: {\n tree: AppTree;\n config: ConfigApi;\n appApiRegistry: FrontendApiRegistry;\n fallbackApis?: ApiHolder;\n includeConfigApi: boolean;\n appBasePath: string;\n routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>;\n staticFactories: AnyApiFactory[];\n}) {\n const appTreeApi = new AppTreeApiProxy(options.tree, options.appBasePath);\n const routeResolutionApi = new RouteResolutionApiProxy(\n options.routeBindings,\n options.appBasePath,\n );\n const identityProxy = new PreparedAppIdentityProxy();\n const appLifecycleApi = new DefaultAppLifecycleApi();\n const phaseApiRegistry = new FrontendApiRegistry();\n phaseApiRegistry.registerAll([\n createApiFactory(appTreeApiRef, appTreeApi),\n createApiFactory(appLifecycleApiRef, appLifecycleApi),\n ...(options.includeConfigApi\n ? [createApiFactory(configApiRef, options.config)]\n : []),\n createApiFactory(routeResolutionApiRef, routeResolutionApi),\n createApiFactory(identityApiRef, identityProxy),\n ...options.staticFactories,\n ]);\n\n const apis = new FrontendApiResolver({\n primaryRegistry: phaseApiRegistry,\n secondaryRegistry: options.appApiRegistry,\n fallbackApis: options.fallbackApis,\n });\n\n return {\n apis,\n routeResolutionApi,\n appTreeApi,\n appLifecycleApi,\n identityApiProxy: identityProxy,\n };\n}\n\nexport function instantiateAndInitializePhaseTree(options: {\n tree: AppTree;\n apis: ApiHolder;\n collector: ErrorCollector;\n extensionFactoryMiddleware?: ExtensionFactoryMiddleware;\n routeResolutionApi: RouteResolutionApiProxy;\n appTreeApi: AppTreeApiProxy;\n routeRefsById: ReturnType<typeof collectRouteIds>;\n skipChild?(ctx: { node: AppNode; input: string; child: AppNode }): boolean;\n onMissingApi?(ctx: { node: AppNode; apiRefId: string }): void;\n predicateContext?: ExtensionPredicateContext;\n stopAtAttachment?(ctx: { node: AppNode; input: string }): boolean;\n}) {\n instantiateAppNodeTree(\n options.tree.root,\n options.apis,\n options.collector,\n options.extensionFactoryMiddleware,\n {\n ...(options.stopAtAttachment\n ? { stopAtAttachment: options.stopAtAttachment }\n : {}),\n skipChild: options.skipChild,\n onMissingApi: options.onMissingApi,\n predicateContext: options.predicateContext,\n },\n );\n\n const routeInfo = extractRouteInfoFromAppNode(\n options.tree.root,\n createRouteAliasResolver(options.routeRefsById),\n );\n\n options.routeResolutionApi.initialize(\n routeInfo,\n options.routeRefsById.routes,\n );\n options.appTreeApi.initialize(routeInfo);\n}\n\nexport function setIdentityApiTarget(options: {\n identityApiProxy: AppIdentityProxy;\n identityApi: IdentityApi;\n signOutTargetUrl: string;\n}) {\n options.identityApiProxy.setTarget(options.identityApi, {\n signOutTargetUrl: options.signOutTargetUrl,\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"predicates.esm.js","sources":["../../src/wiring/predicates.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 {\n ApiHolder,\n createApiRef,\n featureFlagsApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { FilterPredicate } from '@backstage/filter-predicates';\nimport type {\n EvaluatePermissionRequest,\n EvaluatePermissionResponse,\n} from '@backstage/plugin-permission-common';\nimport { ForwardedError } from '@backstage/errors';\n\nexport type ExtensionPredicateContext = {\n featureFlags: string[];\n permissions: string[];\n};\n\nexport const EMPTY_PREDICATE_CONTEXT: ExtensionPredicateContext = {\n featureFlags: [],\n permissions: [],\n};\n\n// Minimal local permission API interface to avoid a dependency on @backstage/plugin-permission-react\ntype MinimalPermissionApi = {\n authorize(\n request: EvaluatePermissionRequest,\n ): Promise<EvaluatePermissionResponse>;\n};\n\nfunction parsePermissionName(permissionName: string) {\n const parts = permissionName.split('#');\n const [name, action, ...rest] = parts;\n\n if (rest.length > 0 || !name || (parts.length === 2 && !action)) {\n throw new Error(\n `Invalid permission name: ${permissionName}. Permission names must be in the format \"permissionName\" or \"permissionName#action\" (both parts must be non-empty).`,\n );\n }\n\n if (action === undefined) {\n return { name };\n }\n\n return {\n name,\n attributes: {\n action,\n },\n };\n}\n\nexport const localPermissionApiRef = createApiRef<MinimalPermissionApi>({\n id: 'plugin.permission.api',\n});\n\nexport function createPredicateContextLoader(options: {\n apis: ApiHolder;\n predicateReferences: ExtensionPredicateContext;\n}) {\n function getActiveFeatureFlags() {\n const featureFlagsApi = options.apis.get(featureFlagsApiRef);\n if (!featureFlagsApi) {\n return [];\n }\n\n return options.predicateReferences.featureFlags.filter(name =>\n featureFlagsApi.isActive(name),\n );\n }\n\n function getImmediate(): ExtensionPredicateContext | undefined {\n if (options.predicateReferences.permissions.length > 0) {\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n return undefined;\n }\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: [],\n };\n }\n\n async function load() {\n const immediatePredicateContext = getImmediate();\n if (immediatePredicateContext) {\n return immediatePredicateContext;\n }\n\n let allowedPermissions: string[] = [];\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n try {\n const permissionNames = options.predicateReferences.permissions;\n const hydratedPermissions = permissionNames.map(name => {\n const { name: permissionName, attributes } =\n parsePermissionName(name);\n const permission = {\n name: permissionName,\n type: 'basic',\n attributes: attributes || {},\n } as const;\n return permission;\n });\n const responses = await Promise.all(\n hydratedPermissions.map(permission =>\n permissionApi.authorize({\n permission,\n }),\n ),\n );\n allowedPermissions = permissionNames.filter(\n (_, i) => responses[i].result === 'ALLOW',\n );\n } catch (error) {\n throw new ForwardedError(\n 'Failed to authorize extension permissions',\n error,\n );\n }\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: allowedPermissions,\n };\n }\n\n return {\n getImmediate,\n load,\n };\n}\n\nexport function collectPredicateReferences(\n nodes: Iterable<{ spec: { if?: FilterPredicate } }>,\n): ExtensionPredicateContext {\n const featureFlags = new Set<string>();\n const permissions = new Set<string>();\n\n for (const node of nodes) {\n if (node.spec.if === undefined) {\n continue;\n }\n\n for (const name of extractFeatureFlagNames(node.spec.if)) {\n featureFlags.add(name);\n }\n for (const name of extractPermissionNames(node.spec.if)) {\n permissions.add(name);\n }\n }\n\n return {\n featureFlags: Array.from(featureFlags),\n permissions: Array.from(permissions),\n };\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `featureFlags: { $contains: '...' }` expressions. This lets us call\n * `isActive()` only for the flags that are actually used in predicates rather\n * than fetching the full registered-flag list.\n */\nfunction extractFeatureFlagNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'featureFlags');\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `permissions: { $contains: '...' }` expressions. This lets us issue a\n * single batched authorize call for only the permissions actually referenced.\n */\nfunction extractPermissionNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'permissions');\n}\n\nfunction extractPredicateKeyNames(\n predicate: FilterPredicate,\n key: string,\n): string[] {\n if (typeof predicate !== 'object' || predicate === null) {\n return [];\n }\n const obj = predicate as Record<string, unknown>;\n if (Array.isArray(obj.$all)) {\n return (obj.$all as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (Array.isArray(obj.$any)) {\n return (obj.$any as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (obj.$not !== undefined) {\n return extractPredicateKeyNames(obj.$not as FilterPredicate, key);\n }\n const value = obj[key];\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const contains = (value as Record<string, unknown>).$contains;\n if (typeof contains === 'string') {\n return [contains];\n }\n }\n return [];\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"predicates.esm.js","sources":["../../src/wiring/predicates.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 {\n ApiHolder,\n createApiRef,\n featureFlagsApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { FilterPredicate } from '@backstage/filter-predicates';\nimport type {\n EvaluatePermissionRequest,\n EvaluatePermissionResponse,\n} from '@backstage/plugin-permission-common';\nimport { ForwardedError } from '@backstage/errors';\n\nexport type ExtensionPredicateContext = {\n featureFlags: string[];\n permissions: string[];\n};\n\nexport const EMPTY_PREDICATE_CONTEXT: ExtensionPredicateContext = {\n featureFlags: [],\n permissions: [],\n};\n\n// Minimal local permission API interface to avoid a dependency on @backstage/plugin-permission-react\ntype MinimalPermissionApi = {\n authorize(\n request: EvaluatePermissionRequest,\n ): Promise<EvaluatePermissionResponse>;\n};\n\nfunction parsePermissionName(permissionName: string) {\n const parts = permissionName.split('#');\n const [name, action, ...rest] = parts;\n\n if (rest.length > 0 || !name || (parts.length === 2 && !action)) {\n throw new Error(\n `Invalid permission name: ${permissionName}. Permission names must be in the format \"permissionName\" or \"permissionName#action\" (both parts must be non-empty).`,\n );\n }\n\n if (action === undefined) {\n return { name };\n }\n\n return {\n name,\n attributes: {\n action,\n },\n };\n}\n\nexport const localPermissionApiRef = createApiRef<MinimalPermissionApi>({\n id: 'plugin.permission.api',\n});\n\nexport function createPredicateContextLoader(options: {\n apis: ApiHolder;\n predicateReferences: ExtensionPredicateContext;\n}) {\n function getActiveFeatureFlags() {\n const featureFlagsApi = options.apis.get(featureFlagsApiRef);\n if (!featureFlagsApi) {\n return [];\n }\n\n return options.predicateReferences.featureFlags.filter(name =>\n featureFlagsApi.isActive(name),\n );\n }\n\n function getImmediate(): ExtensionPredicateContext | undefined {\n if (options.predicateReferences.permissions.length > 0) {\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n return undefined;\n }\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: [],\n };\n }\n\n async function load() {\n const immediatePredicateContext = getImmediate();\n if (immediatePredicateContext) {\n return immediatePredicateContext;\n }\n\n let allowedPermissions: string[] = [];\n const permissionApi = options.apis.get(localPermissionApiRef);\n if (permissionApi) {\n try {\n const permissionNames = options.predicateReferences.permissions;\n const hydratedPermissions = permissionNames.map(name => {\n const { name: permissionName, attributes } =\n parsePermissionName(name);\n const permission = {\n name: permissionName,\n type: 'basic',\n attributes: attributes || {},\n } as const;\n return permission;\n });\n const responses = await Promise.all(\n hydratedPermissions.map(permission =>\n permissionApi.authorize({\n permission,\n }),\n ),\n );\n allowedPermissions = permissionNames.filter(\n (_, i) => responses[i].result === 'ALLOW',\n );\n } catch (error) {\n throw new ForwardedError(\n 'Failed to authorize extension permissions',\n error,\n );\n }\n }\n\n return {\n featureFlags: getActiveFeatureFlags(),\n permissions: allowedPermissions,\n };\n }\n\n return {\n getImmediate,\n load,\n };\n}\n\nexport function collectPredicateReferences(\n nodes: Iterable<{ spec: { if?: FilterPredicate } }>,\n): ExtensionPredicateContext {\n const featureFlags = new Set<string>();\n const permissions = new Set<string>();\n\n for (const node of nodes) {\n if (node.spec.if === undefined) {\n continue;\n }\n\n for (const name of extractFeatureFlagNames(node.spec.if)) {\n featureFlags.add(name);\n }\n for (const name of extractPermissionNames(node.spec.if)) {\n permissions.add(name);\n }\n }\n\n return {\n featureFlags: Array.from(featureFlags),\n permissions: Array.from(permissions),\n };\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `featureFlags: { $contains: '...' }` expressions. This lets us call\n * `isActive()` only for the flags that are actually used in predicates rather\n * than fetching the full registered-flag list.\n */\nfunction extractFeatureFlagNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'featureFlags');\n}\n\n/**\n * Recursively walks a FilterPredicate and returns all string values referenced\n * by `permissions: { $contains: '...' }` expressions. This lets us issue a\n * single batched authorize call for only the permissions actually referenced.\n */\nfunction extractPermissionNames(predicate: FilterPredicate): string[] {\n return extractPredicateKeyNames(predicate, 'permissions');\n}\n\nfunction extractPredicateKeyNames(\n predicate: FilterPredicate,\n key: string,\n): string[] {\n if (typeof predicate !== 'object' || predicate === null) {\n return [];\n }\n const obj = predicate as Record<string, unknown>;\n if (Array.isArray(obj.$all)) {\n return (obj.$all as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (Array.isArray(obj.$any)) {\n return (obj.$any as FilterPredicate[]).flatMap(p =>\n extractPredicateKeyNames(p, key),\n );\n }\n if (obj.$not !== undefined) {\n return extractPredicateKeyNames(obj.$not as FilterPredicate, key);\n }\n const value = obj[key];\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const contains = (value as Record<string, unknown>).$contains;\n if (typeof contains === 'string') {\n return [contains];\n }\n }\n return [];\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -210,13 +210,15 @@ function prepareSpecializedApp(options) {
|
|
|
210
210
|
providedSessionData
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
|
+
function setFinalized(finalizedApp) {
|
|
214
|
+
finalized = finalizedApp;
|
|
215
|
+
phase.appLifecycleApi.markFinalized();
|
|
216
|
+
}
|
|
213
217
|
const finalization = createFinalizationController({
|
|
214
218
|
getFinalized() {
|
|
215
219
|
return finalized;
|
|
216
220
|
},
|
|
217
|
-
setFinalized
|
|
218
|
-
finalized = finalizedApp;
|
|
219
|
-
},
|
|
221
|
+
setFinalized,
|
|
220
222
|
finalizeFromSessionState: finalizeWithSessionState,
|
|
221
223
|
finalizeFromBootstrapError: finalizeWithBootstrapError
|
|
222
224
|
});
|
|
@@ -313,8 +315,9 @@ function prepareSpecializedApp(options) {
|
|
|
313
315
|
"prepareSpecializedApp requires waiting for asynchronous finalization before calling finalize()"
|
|
314
316
|
);
|
|
315
317
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
+
const finalizedApp = finalizeWithSessionState(finalizedSessionState);
|
|
319
|
+
setFinalized(finalizedApp);
|
|
320
|
+
return finalizedApp;
|
|
318
321
|
}
|
|
319
322
|
};
|
|
320
323
|
}
|