@backstage/frontend-app-api 0.11.5-next.1 → 0.12.0-next.3

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 (27) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/config.d.ts +17 -0
  3. package/dist/frontend-internal/src/wiring/InternalExtensionDefinition.esm.js.map +1 -1
  4. package/dist/frontend-internal/src/wiring/InternalSwappableComponentRef.esm.js +7 -0
  5. package/dist/frontend-internal/src/wiring/InternalSwappableComponentRef.esm.js.map +1 -0
  6. package/dist/frontend-internal/src/wiring/createExtensionDataContainer.esm.js +4 -1
  7. package/dist/frontend-internal/src/wiring/createExtensionDataContainer.esm.js.map +1 -1
  8. package/dist/frontend-plugin-api/src/routing/RouteRef.esm.js.map +1 -1
  9. package/dist/frontend-plugin-api/src/wiring/createFrontendModule.esm.js.map +1 -1
  10. package/dist/frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js +1 -0
  11. package/dist/frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js.map +1 -1
  12. package/dist/index.d.ts +62 -17
  13. package/dist/routing/RouteAliasResolver.esm.js +51 -0
  14. package/dist/routing/RouteAliasResolver.esm.js.map +1 -0
  15. package/dist/routing/RouteResolver.esm.js +3 -2
  16. package/dist/routing/RouteResolver.esm.js.map +1 -1
  17. package/dist/routing/collectRouteIds.esm.js +1 -0
  18. package/dist/routing/collectRouteIds.esm.js.map +1 -1
  19. package/dist/routing/extractRouteInfoFromAppNode.esm.js +14 -3
  20. package/dist/routing/extractRouteInfoFromAppNode.esm.js.map +1 -1
  21. package/dist/tree/instantiateAppNodeTree.esm.js +8 -2
  22. package/dist/tree/instantiateAppNodeTree.esm.js.map +1 -1
  23. package/dist/tree/resolveAppNodeSpecs.esm.js +6 -2
  24. package/dist/tree/resolveAppNodeSpecs.esm.js.map +1 -1
  25. package/dist/wiring/createSpecializedApp.esm.js +19 -14
  26. package/dist/wiring/createSpecializedApp.esm.js.map +1 -1
  27. package/package.json +6 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,79 @@
1
1
  # @backstage/frontend-app-api
2
2
 
3
+ ## 0.12.0-next.3
4
+
5
+ ### Minor Changes
6
+
7
+ - 8e21c4d: Use an app plugin for built-in extension app node specs.
8
+ - 8e21c4d: The `AppNodeSpec.plugin` property is now required.
9
+ - 5e12252: **BREAKING**: Restructured some of option fields of `createApp` and `createSpecializedApp`.
10
+
11
+ - For `createApp`, all option fields _except_ `features` and `bindRoutes` have been moved into a new `advanced` object field.
12
+ - For `createSpecializedApp`, all option fields _except_ `features`, `config`, and `bindRoutes` have been moved into a new `advanced` object field.
13
+
14
+ This helps highlight that some options are meant to rarely be needed or used, and simplifies the usage of those options that are almost always required.
15
+
16
+ As an example, if you used to supply a custom config loader, you would update your code as follows:
17
+
18
+ ```diff
19
+ createApp({
20
+ features: [...],
21
+ - configLoader: new MyCustomLoader(),
22
+ + advanced: {
23
+ + configLoader: new MyCustomLoader(),
24
+ + },
25
+ })
26
+ ```
27
+
28
+ ### Patch Changes
29
+
30
+ - f3f9d57: Renaming the `getNodesByRoutePath` parameter from `sourcePath` to `routePath`
31
+ - 8b1bf6e: Deprecated new frontend system config setting `app.experimental.packages` to just `app.packages`. The old config will continue working for the time being, but may be removed in a future release.
32
+ - fda1bbc: Added a default implementation of the `SwappableComponentsApi` and removing the legacy `ComponentsApi` implementation
33
+ - 1c2cc37: Improved runtime error message clarity when extension factories don't return an iterable object.
34
+ - Updated dependencies
35
+ - @backstage/frontend-plugin-api@0.11.0-next.2
36
+ - @backstage/frontend-defaults@0.3.0-next.3
37
+
38
+ ## 0.12.0-next.2
39
+
40
+ ### Minor Changes
41
+
42
+ - df7bd3b: **BREAKING**: Removed the deprecated `FrontendFeature` type, import it from `@backstage/frontend-plugin-api` instead.
43
+
44
+ ### Patch Changes
45
+
46
+ - d9e00e3: Add support for a new `aliasFor` option for `createRouteRef`. This allows for the creation of a new route ref that acts as an alias for an existing route ref that is installed in the app. This is particularly useful when creating modules that override existing plugin pages, without referring to the existing plugin. For example:
47
+
48
+ ```tsx
49
+ export default createFrontendModule({
50
+ pluginId: 'catalog',
51
+ extensions: [
52
+ PageBlueprint.make({
53
+ params: {
54
+ defaultPath: '/catalog',
55
+ routeRef: createRouteRef({ aliasFor: 'catalog.catalogIndex' }),
56
+ loader: () =>
57
+ import('./CustomCatalogIndexPage').then(m => (
58
+ <m.CustomCatalogIndexPage />
59
+ )),
60
+ },
61
+ }),
62
+ ],
63
+ });
64
+ ```
65
+
66
+ - 3d2499f: Moved `createSpecializedApp` options to a new `CreateSpecializedAppOptions` type.
67
+ - Updated dependencies
68
+ - @backstage/frontend-defaults@0.3.0-next.2
69
+ - @backstage/frontend-plugin-api@0.11.0-next.1
70
+ - @backstage/config@1.3.3
71
+ - @backstage/core-app-api@1.18.0
72
+ - @backstage/core-plugin-api@1.10.9
73
+ - @backstage/errors@1.2.7
74
+ - @backstage/types@1.2.1
75
+ - @backstage/version-bridge@1.0.11
76
+
3
77
  ## 0.11.5-next.1
4
78
 
5
79
  ### Patch Changes
package/config.d.ts CHANGED
@@ -20,10 +20,27 @@ export interface Config {
20
20
  /**
21
21
  * @visibility frontend
22
22
  * @deepVisibility frontend
23
+ * @deprecated This is no longer experimental; use `app.packages` instead.
23
24
  */
24
25
  packages?: 'all' | { include?: string[]; exclude?: string[] };
25
26
  };
26
27
 
28
+ /**
29
+ * Controls what packages are loaded by the new frontend system.
30
+ *
31
+ * @remarks
32
+ *
33
+ * When using the 'all' option, all feature packages that were added as
34
+ * dependencies to the app will be loaded automatically.
35
+ *
36
+ * The `include` and `exclude` options can be used to more finely control
37
+ * which individual package names to include or exclude.
38
+ *
39
+ * @visibility frontend
40
+ * @deepVisibility frontend
41
+ */
42
+ packages?: 'all' | { include?: string[]; exclude?: string[] };
43
+
27
44
  routes?: {
28
45
  /**
29
46
  * Maps external route references to regular route references. Both the
@@ -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 AnyExtensionDataRef,\n ApiHolder,\n AppNode,\n ExtensionAttachToSpec,\n ExtensionDataValue,\n ExtensionDefinition,\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: ExtensionDefinition<ExtensionDefinitionParameters>;\n versions:\n | {\n readonly version: 'v1';\n readonly kind?: string;\n readonly namespace?: string;\n readonly name?: string;\n readonly attachTo: ExtensionAttachToSpec;\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]: AnyExtensionDataRef;\n };\n config: { optional: boolean; singleton: boolean };\n };\n };\n readonly output: {\n [name in string]: AnyExtensionDataRef;\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: ExtensionAttachToSpec;\n readonly disabled: boolean;\n readonly configSchema?: PortableSchema<any, any>;\n readonly inputs: {\n [inputName in string]: ExtensionInput<\n AnyExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n };\n readonly output: Array<AnyExtensionDataRef>;\n factory(context: {\n node: AppNode;\n apis: ApiHolder;\n config: object;\n inputs: ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput<\n AnyExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n }>;\n }): Iterable<ExtensionDataValue<any, any>>;\n };\n}>({\n type: '@backstage/ExtensionDefinition',\n versions: ['v1', 'v2'],\n});\n"],"names":[],"mappings":";;AA8Ba,MAAA,yBAAA,GAA4B,WAAW,MA6DjD,CAAA;AAAA,EACD,IAAM,EAAA,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 ExtensionAttachToSpec,\n ExtensionDataValue,\n ExtensionDataRef,\n ExtensionDefinition,\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: ExtensionDefinition<ExtensionDefinitionParameters>;\n versions:\n | {\n readonly version: 'v1';\n readonly kind?: string;\n readonly namespace?: string;\n readonly name?: string;\n readonly attachTo: ExtensionAttachToSpec;\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: ExtensionAttachToSpec;\n readonly disabled: boolean;\n readonly configSchema?: PortableSchema<any, any>;\n readonly inputs: {\n [inputName in string]: ExtensionInput<\n ExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n };\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 ExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n }>;\n }): Iterable<ExtensionDataValue<any, any>>;\n };\n}>({\n type: '@backstage/ExtensionDefinition',\n versions: ['v1', 'v2'],\n});\n"],"names":[],"mappings":";;AA8Ba,MAAA,yBAAA,GAA4B,WAAW,MA6DjD,CAAA;AAAA,EACD,IAAM,EAAA,gCAAA;AAAA,EACN,QAAA,EAAU,CAAC,IAAA,EAAM,IAAI;AACvB,CAAC;;;;"}
@@ -0,0 +1,7 @@
1
+ import { OpaqueType } from '../../../opaque-internal/src/OpaqueType.esm.js';
2
+
3
+ OpaqueType.create({
4
+ versions: ["v1"],
5
+ type: "@backstage/SwappableComponentRef"
6
+ });
7
+ //# sourceMappingURL=InternalSwappableComponentRef.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InternalSwappableComponentRef.esm.js","sources":["../../../../../frontend-internal/src/wiring/InternalSwappableComponentRef.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 { SwappableComponentRef } from '@backstage/frontend-plugin-api';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueSwappableComponentRef = OpaqueType.create<{\n public: SwappableComponentRef;\n versions: {\n readonly version: 'v1';\n readonly transformProps?: (props: object) => object;\n readonly defaultComponent: (props: object) => JSX.Element | null;\n };\n}>({\n versions: ['v1'],\n type: '@backstage/SwappableComponentRef',\n});\n"],"names":[],"mappings":";;AAmB2C,WAAW,MAOnD,CAAA;AAAA,EACD,QAAA,EAAU,CAAC,IAAI,CAAA;AAAA,EACf,IAAM,EAAA;AACR,CAAC"}
@@ -1,4 +1,7 @@
1
- function createExtensionDataContainer(values, declaredRefs) {
1
+ function createExtensionDataContainer(values, contextName, declaredRefs) {
2
+ if (typeof values !== "object" || !values?.[Symbol.iterator]) {
3
+ throw new Error(`${contextName} did not provide an iterable object`);
4
+ }
2
5
  const container = /* @__PURE__ */ new Map();
3
6
  for (const output of values) {
4
7
  container.set(output.id, output);
@@ -1 +1 @@
1
- {"version":3,"file":"createExtensionDataContainer.esm.js","sources":["../../../../../frontend-internal/src/wiring/createExtensionDataContainer.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 AnyExtensionDataRef,\n ExtensionDataContainer,\n ExtensionDataRef,\n ExtensionDataValue,\n} from '@backstage/frontend-plugin-api';\n\nexport function createExtensionDataContainer<UData extends AnyExtensionDataRef>(\n values: Iterable<\n UData extends ExtensionDataRef<infer IData, infer IId>\n ? ExtensionDataValue<IData, IId>\n : never\n >,\n declaredRefs?: ExtensionDataRef<any, any, any>[],\n): ExtensionDataContainer<UData> {\n const container = new Map<string, ExtensionDataValue<any, any>>();\n const verifyRefs =\n declaredRefs && new Map(declaredRefs.map(ref => [ref.id, ref]));\n\n for (const output of values) {\n if (verifyRefs) {\n if (!verifyRefs.delete(output.id)) {\n throw new Error(\n `extension data '${output.id}' was provided but not declared`,\n );\n }\n }\n container.set(output.id, output);\n }\n\n const remainingRefs =\n verifyRefs &&\n Array.from(verifyRefs.values()).filter(ref => !ref.config.optional);\n if (remainingRefs && remainingRefs.length > 0) {\n throw new Error(\n `missing required extension data value(s) '${remainingRefs\n .map(ref => ref.id)\n .join(', ')}'`,\n );\n }\n\n return {\n get(ref) {\n return container.get(ref.id)?.value;\n },\n [Symbol.iterator]() {\n return container.values();\n },\n } as ExtensionDataContainer<UData>;\n}\n"],"names":[],"mappings":"AAuBgB,SAAA,4BAAA,CACd,QAKA,YAC+B,EAAA;AAC/B,EAAM,MAAA,SAAA,uBAAgB,GAA0C,EAAA;AAIhE,EAAA,KAAA,MAAW,UAAU,MAAQ,EAAA;AAQ3B,IAAU,SAAA,CAAA,GAAA,CAAI,MAAO,CAAA,EAAA,EAAI,MAAM,CAAA;AAAA;AAcjC,EAAO,OAAA;AAAA,IACL,IAAI,GAAK,EAAA;AACP,MAAA,OAAO,SAAU,CAAA,GAAA,CAAI,GAAI,CAAA,EAAE,CAAG,EAAA,KAAA;AAAA,KAChC;AAAA,IACA,CAAC,MAAO,CAAA,QAAQ,CAAI,GAAA;AAClB,MAAA,OAAO,UAAU,MAAO,EAAA;AAAA;AAC1B,GACF;AACF;;;;"}
1
+ {"version":3,"file":"createExtensionDataContainer.esm.js","sources":["../../../../../frontend-internal/src/wiring/createExtensionDataContainer.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 ExtensionDataContainer,\n ExtensionDataRef,\n ExtensionDataValue,\n} from '@backstage/frontend-plugin-api';\n\nexport function createExtensionDataContainer<UData extends ExtensionDataRef>(\n values: Iterable<\n UData extends ExtensionDataRef<infer IData, infer IId>\n ? ExtensionDataValue<IData, IId>\n : never\n >,\n contextName: string,\n declaredRefs?: ExtensionDataRef<any, any, any>[],\n): ExtensionDataContainer<UData> {\n if (typeof values !== 'object' || !values?.[Symbol.iterator]) {\n throw new Error(`${contextName} did not provide an iterable object`);\n }\n\n const container = new Map<string, ExtensionDataValue<any, any>>();\n const verifyRefs =\n declaredRefs && new Map(declaredRefs.map(ref => [ref.id, ref]));\n\n for (const output of values) {\n if (verifyRefs) {\n if (!verifyRefs.delete(output.id)) {\n throw new Error(\n `extension data '${output.id}' was provided but not declared`,\n );\n }\n }\n container.set(output.id, output);\n }\n\n const remainingRefs =\n verifyRefs &&\n Array.from(verifyRefs.values()).filter(ref => !ref.config.optional);\n if (remainingRefs && remainingRefs.length > 0) {\n throw new Error(\n `missing required extension data value(s) '${remainingRefs\n .map(ref => ref.id)\n .join(', ')}'`,\n );\n }\n\n return {\n get(ref) {\n return container.get(ref.id)?.value;\n },\n [Symbol.iterator]() {\n return container.values();\n },\n } as ExtensionDataContainer<UData>;\n}\n"],"names":[],"mappings":"AAsBgB,SAAA,4BAAA,CACd,MAKA,EAAA,WAAA,EACA,YAC+B,EAAA;AAC/B,EAAA,IAAI,OAAO,MAAW,KAAA,QAAA,IAAY,CAAC,MAAS,GAAA,MAAA,CAAO,QAAQ,CAAG,EAAA;AAC5D,IAAA,MAAM,IAAI,KAAA,CAAM,CAAG,EAAA,WAAW,CAAqC,mCAAA,CAAA,CAAA;AAAA;AAGrE,EAAM,MAAA,SAAA,uBAAgB,GAA0C,EAAA;AAIhE,EAAA,KAAA,MAAW,UAAU,MAAQ,EAAA;AAQ3B,IAAU,SAAA,CAAA,GAAA,CAAI,MAAO,CAAA,EAAA,EAAI,MAAM,CAAA;AAAA;AAcjC,EAAO,OAAA;AAAA,IACL,IAAI,GAAK,EAAA;AACP,MAAA,OAAO,SAAU,CAAA,GAAA,CAAI,GAAI,CAAA,EAAE,CAAG,EAAA,KAAA;AAAA,KAChC;AAAA,IACA,CAAC,MAAO,CAAA,QAAQ,CAAI,GAAA;AAClB,MAAA,OAAO,UAAU,MAAO,EAAA;AAAA;AAC1B,GACF;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"RouteRef.esm.js","sources":["../../../../../frontend-plugin-api/src/routing/RouteRef.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 { describeParentCallSite } from './describeParentCallSite';\nimport { AnyRouteRefParams } from './types';\n\n/**\n * Absolute route reference.\n *\n * @remarks\n *\n * See {@link https://backstage.io/docs/plugins/composability#routing-system}.\n *\n * @public\n */\nexport interface RouteRef<\n TParams extends AnyRouteRefParams = AnyRouteRefParams,\n> {\n readonly $$type: '@backstage/RouteRef';\n readonly T: TParams;\n}\n\n/** @internal */\nexport interface InternalRouteRef<\n TParams extends AnyRouteRefParams = AnyRouteRefParams,\n> extends RouteRef<TParams> {\n readonly version: 'v1';\n getParams(): string[];\n getDescription(): string;\n\n setId(id: string): void;\n}\n\n/** @internal */\nexport function toInternalRouteRef<\n TParams extends AnyRouteRefParams = AnyRouteRefParams,\n>(resource: RouteRef<TParams>): InternalRouteRef<TParams> {\n const r = resource as InternalRouteRef<TParams>;\n if (r.$$type !== '@backstage/RouteRef') {\n throw new Error(`Invalid RouteRef, bad type '${r.$$type}'`);\n }\n\n return r;\n}\n\n/** @internal */\nexport function isRouteRef(opaque: { $$type: string }): opaque is RouteRef {\n return opaque.$$type === '@backstage/RouteRef';\n}\n\n/** @internal */\nexport class RouteRefImpl implements InternalRouteRef {\n readonly $$type = '@backstage/RouteRef';\n readonly version = 'v1';\n declare readonly T: never;\n\n #id?: string;\n #params: string[];\n #creationSite: string;\n\n constructor(readonly params: string[] = [], creationSite: string) {\n this.#params = params;\n this.#creationSite = creationSite;\n }\n\n getParams(): string[] {\n return this.#params;\n }\n\n getDescription(): string {\n if (this.#id) {\n return this.#id;\n }\n return `created at '${this.#creationSite}'`;\n }\n\n get #name() {\n return this.$$type.slice('@backstage/'.length);\n }\n\n setId(id: string): void {\n if (!id) {\n throw new Error(`${this.#name} id must be a non-empty string`);\n }\n if (this.#id && this.#id !== id) {\n throw new Error(\n `${this.#name} was referenced twice as both '${this.#id}' and '${id}'`,\n );\n }\n this.#id = id;\n }\n\n toString(): string {\n return `${this.#name}{${this.getDescription()}}`;\n }\n}\n\n/**\n * Create a {@link RouteRef} from a route descriptor.\n *\n * @param config - Description of the route reference to be created.\n * @public\n */\nexport function createRouteRef<\n // Params is the type that we care about and the one to be embedded in the route ref.\n // For example, given the params ['name', 'kind'], Params will be {name: string, kind: string}\n TParams extends { [param in TParamKeys]: string } | undefined = undefined,\n TParamKeys extends string = string,\n>(config?: {\n /** A list of parameter names that the path that this route ref is bound to must contain */\n readonly params: string extends TParamKeys ? (keyof TParams)[] : TParamKeys[];\n}): RouteRef<\n keyof TParams extends never\n ? undefined\n : string extends TParamKeys\n ? TParams\n : { [param in TParamKeys]: string }\n> {\n return new RouteRefImpl(\n config?.params as string[] | undefined,\n describeParentCallSite(),\n ) as RouteRef<any>;\n}\n"],"names":[],"mappings":"AA+CO,SAAS,mBAEd,QAAwD,EAAA;AACxD,EAAA,MAAM,CAAI,GAAA,QAAA;AACV,EAAI,IAAA,CAAA,CAAE,WAAW,qBAAuB,EAAA;AACtC,IAAA,MAAM,IAAI,KAAA,CAAM,CAA+B,4BAAA,EAAA,CAAA,CAAE,MAAM,CAAG,CAAA,CAAA,CAAA;AAAA;AAG5D,EAAO,OAAA,CAAA;AACT;AAGO,SAAS,WAAW,MAAgD,EAAA;AACzE,EAAA,OAAO,OAAO,MAAW,KAAA,qBAAA;AAC3B;;;;"}
1
+ {"version":3,"file":"RouteRef.esm.js","sources":["../../../../../frontend-plugin-api/src/routing/RouteRef.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 { describeParentCallSite } from './describeParentCallSite';\nimport { AnyRouteRefParams } from './types';\n\n/**\n * Absolute route reference.\n *\n * @remarks\n *\n * See {@link https://backstage.io/docs/plugins/composability#routing-system}.\n *\n * @public\n */\nexport interface RouteRef<\n TParams extends AnyRouteRefParams = AnyRouteRefParams,\n> {\n readonly $$type: '@backstage/RouteRef';\n readonly T: TParams;\n}\n\n/** @internal */\nexport interface InternalRouteRef<\n TParams extends AnyRouteRefParams = AnyRouteRefParams,\n> extends RouteRef<TParams> {\n readonly version: 'v1';\n getParams(): string[];\n getDescription(): string;\n\n alias: string | undefined;\n\n setId(id: string): void;\n}\n\n/** @internal */\nexport function toInternalRouteRef<\n TParams extends AnyRouteRefParams = AnyRouteRefParams,\n>(resource: RouteRef<TParams>): InternalRouteRef<TParams> {\n const r = resource as InternalRouteRef<TParams>;\n if (r.$$type !== '@backstage/RouteRef') {\n throw new Error(`Invalid RouteRef, bad type '${r.$$type}'`);\n }\n\n return r;\n}\n\n/** @internal */\nexport function isRouteRef(opaque: { $$type: string }): opaque is RouteRef {\n return opaque.$$type === '@backstage/RouteRef';\n}\n\n/** @internal */\nexport class RouteRefImpl implements InternalRouteRef {\n readonly $$type = '@backstage/RouteRef';\n readonly version = 'v1';\n declare readonly T: never;\n\n #id?: string;\n readonly #params: string[];\n readonly #creationSite: string;\n readonly #alias?: string;\n\n constructor(\n readonly params: string[] = [],\n creationSite: string,\n alias?: string,\n ) {\n this.#params = params;\n this.#creationSite = creationSite;\n this.#alias = alias;\n }\n\n getParams(): string[] {\n return this.#params;\n }\n\n get alias(): string | undefined {\n return this.#alias;\n }\n\n getDescription(): string {\n if (this.#id) {\n return this.#id;\n }\n return `created at '${this.#creationSite}'`;\n }\n\n get #name() {\n return this.$$type.slice('@backstage/'.length);\n }\n\n setId(id: string): void {\n if (!id) {\n throw new Error(`${this.#name} id must be a non-empty string`);\n }\n if (this.#id && this.#id !== id) {\n throw new Error(\n `${this.#name} was referenced twice as both '${this.#id}' and '${id}'`,\n );\n }\n this.#id = id;\n }\n\n toString(): string {\n return `${this.#name}{${this.getDescription()}}`;\n }\n}\n\n/**\n * Create a {@link RouteRef} from a route descriptor.\n *\n * @param config - Description of the route reference to be created.\n * @public\n */\nexport function createRouteRef<\n // Params is the type that we care about and the one to be embedded in the route ref.\n // For example, given the params ['name', 'kind'], Params will be {name: string, kind: string}\n TParams extends { [param in TParamKeys]: string } | undefined = undefined,\n TParamKeys extends string = string,\n>(config?: {\n /** A list of parameter names that the path that this route ref is bound to must contain */\n readonly params?: string extends TParamKeys\n ? (keyof TParams)[]\n : TParamKeys[];\n\n aliasFor?: string;\n}): RouteRef<\n keyof TParams extends never\n ? undefined\n : string extends TParamKeys\n ? TParams\n : { [param in TParamKeys]: string }\n> {\n return new RouteRefImpl(\n config?.params as string[] | undefined,\n describeParentCallSite(),\n config?.aliasFor,\n ) as RouteRef<any>;\n}\n"],"names":[],"mappings":"AAiDO,SAAS,mBAEd,QAAwD,EAAA;AACxD,EAAA,MAAM,CAAI,GAAA,QAAA;AACV,EAAI,IAAA,CAAA,CAAE,WAAW,qBAAuB,EAAA;AACtC,IAAA,MAAM,IAAI,KAAA,CAAM,CAA+B,4BAAA,EAAA,CAAA,CAAE,MAAM,CAAG,CAAA,CAAA,CAAA;AAAA;AAG5D,EAAO,OAAA,CAAA;AACT;AAGO,SAAS,WAAW,MAAgD,EAAA;AACzE,EAAA,OAAO,OAAO,MAAW,KAAA,qBAAA;AAC3B;;;;"}
@@ -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/** @public */\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":"AAkGO,SAAS,yBAAyB,MAEJ,EAAA;AACnC,EAAI,IAAA,MAAA,CAAO,WAAW,2BAA6B,EAAA;AAEjD,IAAA,wBAAA,CAAyB,MAAwB,CAAA;AACjD,IAAO,OAAA,IAAA;AAAA;AAET,EAAO,OAAA,KAAA;AACT;AAGO,SAAS,yBACd,MACwB,EAAA;AACxB,EAAA,MAAM,QAAW,GAAA,MAAA;AACjB,EAAI,IAAA,QAAA,CAAS,WAAW,2BAA6B,EAAA;AACnD,IAAA,MAAM,IAAI,KAAA,CAAM,CAAsC,mCAAA,EAAA,QAAA,CAAS,MAAM,CAAG,CAAA,CAAA,CAAA;AAAA;AAE1E,EAAI,IAAA,QAAA,CAAS,YAAY,IAAM,EAAA;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,SAAS,OAAO,CAAA,CAAA;AAAA,KAC3D;AAAA;AAEF,EAAO,OAAA,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';\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,MAEJ,EAAA;AACnC,EAAI,IAAA,MAAA,CAAO,WAAW,2BAA6B,EAAA;AAEjD,IAAA,wBAAA,CAAyB,MAAwB,CAAA;AACjD,IAAO,OAAA,IAAA;AAAA;AAET,EAAO,OAAA,KAAA;AACT;AAGO,SAAS,yBACd,MACwB,EAAA;AACxB,EAAA,MAAM,QAAW,GAAA,MAAA;AACjB,EAAI,IAAA,QAAA,CAAS,WAAW,2BAA6B,EAAA;AACnD,IAAA,MAAM,IAAI,KAAA,CAAM,CAAsC,mCAAA,EAAA,QAAA,CAAS,MAAM,CAAG,CAAA,CAAA,CAAA;AAAA;AAE1E,EAAI,IAAA,QAAA,CAAS,YAAY,IAAM,EAAA;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,SAAS,OAAO,CAAA,CAAA;AAAA,KAC3D;AAAA;AAEF,EAAO,OAAA,QAAA;AACT;;;;"}
@@ -1,4 +1,5 @@
1
1
  import { OpaqueExtensionDefinition } from '../../../frontend-internal/src/wiring/InternalExtensionDefinition.esm.js';
2
+ import '../../../frontend-internal/src/wiring/InternalSwappableComponentRef.esm.js';
2
3
  import '../../../frontend-internal/src/wiring/InternalFrontendPlugin.esm.js';
3
4
 
4
5
  function toInternalExtension(overrides) {
@@ -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 ExtensionAttachToSpec,\n ExtensionDefinition,\n ExtensionDefinitionParameters,\n ResolvedExtensionInputs,\n} from './createExtension';\nimport { PortableSchema } from '../schema';\nimport { ExtensionInput } from './createExtensionInput';\nimport {\n AnyExtensionDataRef,\n ExtensionDataValue,\n} from './createExtensionDataRef';\nimport { OpaqueExtensionDefinition } from '@internal/frontend';\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]: AnyExtensionDataRef;\n };\n config: { optional: boolean; singleton: boolean };\n };\n };\n readonly output: {\n [name in string]: AnyExtensionDataRef;\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: {\n [inputName in string]: ExtensionInput<\n AnyExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n };\n readonly output: Array<AnyExtensionDataRef>;\n factory(options: {\n apis: ApiHolder;\n node: AppNode;\n config: TConfig;\n inputs: ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput<\n AnyExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\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\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 const {\n name,\n kind,\n namespace: _skip1,\n override: _skip2,\n ...rest\n } = internalDefinition;\n\n const namespace = internalDefinition.namespace ?? context?.namespace;\n\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 const id = kind ? `${kind}:${namePart}` : namePart;\n\n return {\n ...rest,\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":";;;AA+FO,SAAS,oBACd,SAC0C,EAAA;AAC1C,EAAA,MAAM,QAAW,GAAA,SAAA;AACjB,EAAI,IAAA,QAAA,CAAS,WAAW,sBAAwB,EAAA;AAC9C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,SAAS,MAAM,CAAA,CAAA;AAAA,KAC1D;AAAA;AAEF,EAAA,MAAM,UAAU,QAAS,CAAA,OAAA;AACzB,EAAI,IAAA,OAAA,KAAY,IAAQ,IAAA,OAAA,KAAY,IAAM,EAAA;AACxC,IAAA,MAAM,IAAI,KAAA,CAAM,CAA4C,yCAAA,EAAA,OAAO,CAAG,CAAA,CAAA,CAAA;AAAA;AAExE,EAAO,OAAA,QAAA;AACT;AAuBgB,SAAA,0BAAA,CAGd,YACA,OAC0C,EAAA;AAC1C,EAAM,MAAA,kBAAA,GAAqB,yBAA0B,CAAA,UAAA,CAAW,UAAU,CAAA;AAC1E,EAAM,MAAA;AAAA,IACJ,IAAA;AAAA,IACA,IAAA;AAAA,IACA,SAAW,EAAA,MAAA;AAAA,IACX,QAAU,EAAA,MAAA;AAAA,IACV,GAAG;AAAA,GACD,GAAA,kBAAA;AAEJ,EAAM,MAAA,SAAA,GAAY,kBAAmB,CAAA,SAAA,IAAa,OAAS,EAAA,SAAA;AAE3D,EAAM,MAAA,QAAA,GACJ,QAAQ,SAAY,GAAA,CAAA,EAAG,SAAS,CAAI,CAAA,EAAA,IAAI,KAAK,SAAa,IAAA,IAAA;AAC5D,EAAA,IAAI,CAAC,QAAU,EAAA;AACb,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAuG,oGAAA,EAAA,IAAI,CAAc,WAAA,EAAA,SAAS,SAAS,IAAI,CAAA;AAAA,KACjJ;AAAA;AAGF,EAAA,MAAM,KAAK,IAAO,GAAA,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,QAAQ,CAAK,CAAA,GAAA,QAAA;AAE1C,EAAO,OAAA;AAAA,IACL,GAAG,IAAA;AAAA,IACH,MAAQ,EAAA,sBAAA;AAAA,IACR,SAAS,kBAAmB,CAAA,OAAA;AAAA,IAC5B,EAAA;AAAA,IACA,QAAW,GAAA;AACT,MAAA,OAAO,gBAAgB,EAAE,CAAA,CAAA,CAAA;AAAA;AAC3B,GACF;AACF;;;;"}
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 ExtensionAttachToSpec,\n ExtensionDefinition,\n ExtensionDefinitionParameters,\n ResolvedExtensionInputs,\n} from './createExtension';\nimport { PortableSchema } from '../schema';\nimport { ExtensionInput } from './createExtensionInput';\nimport { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef';\nimport { OpaqueExtensionDefinition } from '@internal/frontend';\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: {\n [inputName in string]: ExtensionInput<\n ExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n };\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 ExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\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\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 const {\n name,\n kind,\n namespace: _skip1,\n override: _skip2,\n ...rest\n } = internalDefinition;\n\n const namespace = internalDefinition.namespace ?? context?.namespace;\n\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 const id = kind ? `${kind}:${namePart}` : namePart;\n\n return {\n ...rest,\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":";;;;AA4FO,SAAS,oBACd,SAC0C,EAAA;AAC1C,EAAA,MAAM,QAAW,GAAA,SAAA;AACjB,EAAI,IAAA,QAAA,CAAS,WAAW,sBAAwB,EAAA;AAC9C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,sCAAA,EAAyC,SAAS,MAAM,CAAA,CAAA;AAAA,KAC1D;AAAA;AAEF,EAAA,MAAM,UAAU,QAAS,CAAA,OAAA;AACzB,EAAI,IAAA,OAAA,KAAY,IAAQ,IAAA,OAAA,KAAY,IAAM,EAAA;AACxC,IAAA,MAAM,IAAI,KAAA,CAAM,CAA4C,yCAAA,EAAA,OAAO,CAAG,CAAA,CAAA,CAAA;AAAA;AAExE,EAAO,OAAA,QAAA;AACT;AAuBgB,SAAA,0BAAA,CAGd,YACA,OAC0C,EAAA;AAC1C,EAAM,MAAA,kBAAA,GAAqB,yBAA0B,CAAA,UAAA,CAAW,UAAU,CAAA;AAC1E,EAAM,MAAA;AAAA,IACJ,IAAA;AAAA,IACA,IAAA;AAAA,IACA,SAAW,EAAA,MAAA;AAAA,IACX,QAAU,EAAA,MAAA;AAAA,IACV,GAAG;AAAA,GACD,GAAA,kBAAA;AAEJ,EAAM,MAAA,SAAA,GAAY,kBAAmB,CAAA,SAAA,IAAa,OAAS,EAAA,SAAA;AAE3D,EAAM,MAAA,QAAA,GACJ,QAAQ,SAAY,GAAA,CAAA,EAAG,SAAS,CAAI,CAAA,EAAA,IAAI,KAAK,SAAa,IAAA,IAAA;AAC5D,EAAA,IAAI,CAAC,QAAU,EAAA;AACb,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAuG,oGAAA,EAAA,IAAI,CAAc,WAAA,EAAA,SAAS,SAAS,IAAI,CAAA;AAAA,KACjJ;AAAA;AAGF,EAAA,MAAM,KAAK,IAAO,GAAA,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,QAAQ,CAAK,CAAA,GAAA,QAAA;AAE1C,EAAO,OAAA;AAAA,IACL,GAAG,IAAA;AAAA,IACH,MAAQ,EAAA,sBAAA;AAAA,IACR,SAAS,kBAAmB,CAAA,OAAA;AAAA,IAC5B,EAAA;AAAA,IACA,QAAW,GAAA;AACT,MAAA,OAAO,gBAAgB,EAAE,CAAA,CAAA,CAAA;AAAA;AAC3B,GACF;AACF;;;;"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ExternalRouteRef, RouteRef, SubRouteRef, FrontendPluginInfo, FrontendFeature as FrontendFeature$1, ExtensionFactoryMiddleware, AppTree } from '@backstage/frontend-plugin-api';
1
+ import { ExternalRouteRef, RouteRef, SubRouteRef, FrontendPluginInfo, FrontendFeature, ExtensionFactoryMiddleware, AppTree } from '@backstage/frontend-plugin-api';
2
2
  import { ConfigApi, ApiHolder } from '@backstage/core-plugin-api';
3
3
  import { JsonObject } from '@backstage/types';
4
4
 
@@ -59,32 +59,77 @@ type FrontendPluginInfoResolver = (ctx: {
59
59
  }>;
60
60
 
61
61
  /**
62
- * Creates an empty app without any default features. This is a low-level API is
63
- * intended for use in tests or specialized setups. Typically you want to use
64
- * `createApp` from `@backstage/frontend-defaults` instead.
62
+ * Options for {@link createSpecializedApp}.
65
63
  *
66
64
  * @public
67
65
  */
68
- declare function createSpecializedApp(options?: {
69
- features?: FrontendFeature$1[];
66
+ type CreateSpecializedAppOptions = {
67
+ /**
68
+ * The list of features to load.
69
+ */
70
+ features?: FrontendFeature[];
71
+ /**
72
+ * The config API implementation to use. For most normal apps, this should be
73
+ * specified.
74
+ *
75
+ * If none is given, a new _empty_ config will be used during startup. In
76
+ * later stages of the app lifecycle, the config API in the API holder will be
77
+ * used.
78
+ */
70
79
  config?: ConfigApi;
80
+ /**
81
+ * Allows for the binding of plugins' external route refs within the app.
82
+ */
71
83
  bindRoutes?(context: {
72
84
  bind: CreateAppRouteBinder;
73
85
  }): void;
74
- apis?: ApiHolder;
75
- extensionFactoryMiddleware?: ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[];
76
- flags?: {
86
+ /**
87
+ * Advanced, more rarely used options.
88
+ */
89
+ advanced?: {
90
+ /**
91
+ * A replacement API holder implementation to use.
92
+ *
93
+ * By default, a new API holder will be constructed automatically based on
94
+ * the other inputs. If you pass in a custom one here, none of that
95
+ * automation will take place - so you will have to take care to supply all
96
+ * those APIs yourself.
97
+ */
98
+ apis?: ApiHolder;
99
+ /**
100
+ * If set to true, the system will silently accept and move on if
101
+ * encountering config for extensions that do not exist. The default is to
102
+ * reject such config to help catch simple mistakes.
103
+ *
104
+ * This flag can be useful in some scenarios where you have a dynamic set of
105
+ * extensions enabled at different times, but also increases the risk of
106
+ * accidentally missing e.g. simple typos in your config.
107
+ */
77
108
  allowUnknownExtensionConfig?: boolean;
109
+ /**
110
+ * Applies one or more middleware on every extension, as they are added to
111
+ * the application.
112
+ *
113
+ * This is an advanced use case for modifying extension data on the fly as
114
+ * it gets emitted by extensions being instantiated.
115
+ */
116
+ extensionFactoryMiddleware?: ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[];
117
+ /**
118
+ * Allows for customizing how plugin info is retrieved.
119
+ */
120
+ pluginInfoResolver?: FrontendPluginInfoResolver;
78
121
  };
79
- pluginInfoResolver?: FrontendPluginInfoResolver;
80
- }): {
122
+ };
123
+ /**
124
+ * Creates an empty app without any default features. This is a low-level API is
125
+ * intended for use in tests or specialized setups. Typically you want to use
126
+ * `createApp` from `@backstage/frontend-defaults` instead.
127
+ *
128
+ * @public
129
+ */
130
+ declare function createSpecializedApp(options?: CreateSpecializedAppOptions): {
81
131
  apis: ApiHolder;
82
132
  tree: AppTree;
83
133
  };
84
134
 
85
- /** @public
86
- * @deprecated Use {@link @backstage/frontend-plugin-api#FrontendFeature} instead.
87
- */
88
- type FrontendFeature = FrontendFeature$1;
89
-
90
- export { type CreateAppRouteBinder, type FrontendFeature, type FrontendPluginInfoResolver, createSpecializedApp };
135
+ export { type CreateAppRouteBinder, type CreateSpecializedAppOptions, type FrontendPluginInfoResolver, createSpecializedApp };
@@ -0,0 +1,51 @@
1
+ import { toInternalRouteRef } from '../frontend-plugin-api/src/routing/RouteRef.esm.js';
2
+
3
+ function createRouteAliasResolver(routeRefsById) {
4
+ const resolver = (routeRef, pluginId) => {
5
+ if (!routeRef) {
6
+ return void 0;
7
+ }
8
+ let currentRef = routeRef;
9
+ for (let i = 0; i < 100; i++) {
10
+ const alias = toInternalRouteRef(currentRef).alias;
11
+ if (alias) {
12
+ if (pluginId) {
13
+ const [aliasPluginId] = alias.split(".");
14
+ if (aliasPluginId !== pluginId) {
15
+ throw new Error(
16
+ `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}'`
17
+ );
18
+ }
19
+ }
20
+ const aliasRef = routeRefsById.routes.get(alias);
21
+ if (!aliasRef) {
22
+ throw new Error(
23
+ `Unable to resolve RouteRef alias '${alias}' for ${currentRef}`
24
+ );
25
+ }
26
+ if (aliasRef.$$type === "@backstage/SubRouteRef") {
27
+ throw new Error(
28
+ `RouteRef alias '${alias}' for ${currentRef} points to a SubRouteRef, which is not supported`
29
+ );
30
+ }
31
+ currentRef = aliasRef;
32
+ } else {
33
+ return currentRef;
34
+ }
35
+ }
36
+ throw new Error(`Alias loop detected for ${routeRef}`);
37
+ };
38
+ return resolver;
39
+ }
40
+ function createExactRouteAliasResolver(routeAliases) {
41
+ const resolver = (routeRef) => {
42
+ if (routeRef && routeAliases.has(routeRef)) {
43
+ return routeAliases.get(routeRef);
44
+ }
45
+ return routeRef;
46
+ };
47
+ return resolver;
48
+ }
49
+
50
+ export { createExactRouteAliasResolver, createRouteAliasResolver };
51
+ //# sourceMappingURL=RouteAliasResolver.esm.js.map
@@ -0,0 +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';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalRouteRef } from '../../../frontend-plugin-api/src/routing/RouteRef';\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 = toInternalRouteRef(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":";;AAiCO,SAAS,yBACd,aACoB,EAAA;AACpB,EAAM,MAAA,QAAA,GAAW,CAAC,QAAA,EAAgC,QAAsB,KAAA;AACtE,IAAA,IAAI,CAAC,QAAU,EAAA;AACb,MAAO,OAAA,KAAA,CAAA;AAAA;AAGT,IAAA,IAAI,UAAa,GAAA,QAAA;AACjB,IAAA,KAAA,IAAS,CAAI,GAAA,CAAA,EAAG,CAAI,GAAA,GAAA,EAAK,CAAK,EAAA,EAAA;AAC5B,MAAM,MAAA,KAAA,GAAQ,kBAAmB,CAAA,UAAU,CAAE,CAAA,KAAA;AAC7C,MAAA,IAAI,KAAO,EAAA;AACT,QAAA,IAAI,QAAU,EAAA;AACZ,UAAA,MAAM,CAAC,aAAa,CAAI,GAAA,KAAA,CAAM,MAAM,GAAG,CAAA;AACvC,UAAA,IAAI,kBAAkB,QAAU,EAAA;AAC9B,YAAA,MAAM,IAAI,KAAA;AAAA,cACR,6BAA6B,KAAK,CAAA,MAAA,EAAS,UAAU,CAAgE,6DAAA,EAAA,QAAQ,8BAA8B,aAAa,CAAA,CAAA;AAAA,aAC1K;AAAA;AACF;AAEF,QAAA,MAAM,QAAW,GAAA,aAAA,CAAc,MAAO,CAAA,GAAA,CAAI,KAAK,CAAA;AAC/C,QAAA,IAAI,CAAC,QAAU,EAAA;AACb,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,kCAAA,EAAqC,KAAK,CAAA,MAAA,EAAS,UAAU,CAAA;AAAA,WAC/D;AAAA;AAEF,QAAI,IAAA,QAAA,CAAS,WAAW,wBAA0B,EAAA;AAChD,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,gBAAA,EAAmB,KAAK,CAAA,MAAA,EAAS,UAAU,CAAA,gDAAA;AAAA,WAC7C;AAAA;AAEF,QAAa,UAAA,GAAA,QAAA;AAAA,OACR,MAAA;AACL,QAAO,OAAA,UAAA;AAAA;AACT;AAEF,IAAA,MAAM,IAAI,KAAA,CAAM,CAA2B,wBAAA,EAAA,QAAQ,CAAE,CAAA,CAAA;AAAA,GACvD;AAEA,EAAO,OAAA,QAAA;AACT;AAMO,SAAS,8BACd,YACoB,EAAA;AACpB,EAAM,MAAA,QAAA,GAAW,CAAC,QAAwB,KAAA;AACxC,IAAA,IAAI,QAAY,IAAA,YAAA,CAAa,GAAI,CAAA,QAAQ,CAAG,EAAA;AAC1C,MAAO,OAAA,YAAA,CAAa,IAAI,QAAQ,CAAA;AAAA;AAElC,IAAO,OAAA,QAAA;AAAA,GACT;AACA,EAAO,OAAA,QAAA;AACT;;;;"}
@@ -81,16 +81,17 @@ function resolveBasePath(targetRef, sourceLocation, routePaths, routeParents, ro
81
81
  return `${joinPaths(parentPath, ...diffPaths)}/`;
82
82
  }
83
83
  class RouteResolver {
84
- constructor(routePaths, routeParents, routeObjects, routeBindings, appBasePath) {
84
+ constructor(routePaths, routeParents, routeObjects, routeBindings, appBasePath, routeAliasResolver) {
85
85
  this.routePaths = routePaths;
86
86
  this.routeParents = routeParents;
87
87
  this.routeObjects = routeObjects;
88
88
  this.routeBindings = routeBindings;
89
89
  this.appBasePath = appBasePath;
90
+ this.routeAliasResolver = routeAliasResolver;
90
91
  }
91
92
  resolve(anyRouteRef, options) {
92
93
  const [targetRef, targetPath] = resolveTargetRef(
93
- anyRouteRef,
94
+ anyRouteRef?.$$type === "@backstage/RouteRef" ? this.routeAliasResolver(anyRouteRef) : anyRouteRef,
94
95
  this.routePaths,
95
96
  this.routeBindings
96
97
  );
@@ -1 +1 @@
1
- {"version":3,"file":"RouteResolver.esm.js","sources":["../../src/routing/RouteResolver.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 { generatePath, matchRoutes } from 'react-router-dom';\nimport {\n RouteRef,\n ExternalRouteRef,\n SubRouteRef,\n AnyRouteRefParams,\n RouteFunc,\n RouteResolutionApiResolveOptions,\n RouteResolutionApi,\n} from '@backstage/frontend-plugin-api';\nimport mapValues from 'lodash/mapValues';\nimport { AnyRouteRef, BackstageRouteObject } from './types';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { isRouteRef } from '../../../frontend-plugin-api/src/routing/RouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isSubRouteRef,\n toInternalSubRouteRef,\n} from '../../../frontend-plugin-api/src/routing/SubRouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { isExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef';\n\n// Joins a list of paths together, avoiding trailing and duplicate slashes\nexport function joinPaths(...paths: string[]): string {\n const normalized = paths.join('/').replace(/\\/\\/+/g, '/');\n if (normalized !== '/' && normalized.endsWith('/')) {\n return normalized.slice(0, -1);\n }\n return normalized;\n}\n\n/**\n * Resolves the absolute route ref that our target route ref is pointing pointing to, as well\n * as the relative target path.\n *\n * Returns an undefined target ref if one could not be fully resolved.\n */\nfunction resolveTargetRef(\n anyRouteRef: AnyRouteRef,\n routePaths: Map<RouteRef, string>,\n routeBindings: Map<AnyRouteRef, AnyRouteRef | undefined>,\n): readonly [RouteRef | undefined, string] {\n // First we figure out which absolute route ref we're dealing with, an if there was an sub route path to append.\n // For sub routes it will be the parent path, while for external routes it will be the bound route.\n let targetRef: RouteRef;\n let subRoutePath = '';\n if (isRouteRef(anyRouteRef)) {\n targetRef = anyRouteRef;\n } else if (isSubRouteRef(anyRouteRef)) {\n const internal = toInternalSubRouteRef(anyRouteRef);\n targetRef = internal.getParent();\n subRoutePath = internal.path;\n } else if (isExternalRouteRef(anyRouteRef)) {\n const resolvedRoute = routeBindings.get(anyRouteRef);\n if (!resolvedRoute) {\n return [undefined, ''];\n }\n if (isRouteRef(resolvedRoute)) {\n targetRef = resolvedRoute;\n } else if (isSubRouteRef(resolvedRoute)) {\n const internal = toInternalSubRouteRef(resolvedRoute);\n targetRef = internal.getParent();\n subRoutePath = resolvedRoute.path;\n } else {\n throw new Error(\n `ExternalRouteRef was bound to invalid target, ${resolvedRoute}`,\n );\n }\n } else {\n throw new Error(`Unknown object passed to useRouteRef, got ${anyRouteRef}`);\n }\n\n // Bail if no absolute path could be resolved\n if (!targetRef) {\n return [undefined, ''];\n }\n\n // Find the path that our target route is bound to\n const resolvedPath = routePaths.get(targetRef);\n if (resolvedPath === undefined) {\n return [undefined, ''];\n }\n\n // SubRouteRefs join the path from the parent route with its own path\n const targetPath = joinPaths(resolvedPath, subRoutePath);\n return [targetRef, targetPath];\n}\n\n/**\n * Resolves the complete base path for navigating to the target RouteRef.\n */\nfunction resolveBasePath(\n targetRef: RouteRef,\n sourceLocation: Parameters<typeof matchRoutes>[1],\n routePaths: Map<RouteRef, string>,\n routeParents: Map<RouteRef, RouteRef | undefined>,\n routeObjects: BackstageRouteObject[],\n) {\n // While traversing the app element tree we build up the routeObjects structure\n // used here. It is the same kind of structure that react-router creates, with the\n // addition that associated route refs are stored throughout the tree. This lets\n // us look up all route refs that can be reached from our source location.\n // Because of the similar route object structure, we can use `matchRoutes` from\n // react-router to do the lookup of our current location.\n const match = matchRoutes(routeObjects, sourceLocation) ?? [];\n\n // While we search for a common routing root between our current location and\n // the target route, we build a list of all route refs we find that we need\n // to traverse to reach the target.\n const refDiffList = Array<RouteRef>();\n\n let matchIndex = -1;\n for (\n let targetSearchRef: RouteRef | undefined = targetRef;\n targetSearchRef;\n targetSearchRef = routeParents.get(targetSearchRef)\n ) {\n // The match contains a list of all ancestral route refs present at our current location\n // Starting at the desired target ref and traversing back through its parents, we search\n // for a target ref that is present in the match for our current location. When a match\n // is found it means we have found a common base to resolve the route from.\n matchIndex = match.findIndex(m =>\n (m.route as BackstageRouteObject).routeRefs.has(targetSearchRef!),\n );\n if (matchIndex !== -1) {\n break;\n }\n\n // Every time we move a step up in the ancestry of the target ref, we add the current ref\n // to the diff list, which ends up being the list of route refs to traverse form the common base\n // in order to reach our target.\n refDiffList.unshift(targetSearchRef);\n }\n\n // If our target route is present in the initial match we need to construct the final path\n // from the parent of the matched route segment. That's to allow the caller of the route\n // function to supply their own params.\n if (refDiffList.length === 0) {\n matchIndex -= 1;\n }\n\n // This is the part of the route tree that the target and source locations have in common.\n // We re-use the existing pathname directly along with all params.\n const parentPath = matchIndex === -1 ? '' : match[matchIndex].pathname;\n\n // This constructs the mid section of the path using paths resolved from all route refs\n // we need to traverse to reach our target except for the very last one. None of these\n // paths are allowed to require any parameters, as the caller would have no way of knowing\n // what parameters those are.\n const diffPaths = refDiffList.slice(0, -1).map(ref => {\n const path = routePaths.get(ref);\n if (path === undefined) {\n throw new Error(`No path for ${ref}`);\n }\n if (path.includes(':')) {\n throw new Error(\n `Cannot route to ${targetRef} with parent ${ref} as it has parameters`,\n );\n }\n return path;\n });\n\n return `${joinPaths(parentPath, ...diffPaths)}/`;\n}\n\nexport class RouteResolver implements RouteResolutionApi {\n constructor(\n private readonly routePaths: Map<RouteRef, string>,\n private readonly routeParents: Map<RouteRef, RouteRef | undefined>,\n private readonly routeObjects: BackstageRouteObject[],\n private readonly routeBindings: Map<\n ExternalRouteRef,\n RouteRef | SubRouteRef\n >,\n private readonly appBasePath: string, // base path without a trailing slash\n ) {}\n\n resolve<TParams extends AnyRouteRefParams>(\n anyRouteRef:\n | RouteRef<TParams>\n | SubRouteRef<TParams>\n | ExternalRouteRef<TParams>,\n options?: RouteResolutionApiResolveOptions,\n ): RouteFunc<TParams> | undefined {\n // First figure out what our target absolute ref is, as well as our target path.\n const [targetRef, targetPath] = resolveTargetRef(\n anyRouteRef,\n this.routePaths,\n this.routeBindings,\n );\n if (!targetRef) {\n return undefined;\n }\n\n // The location that we get passed in uses the full path, so start by trimming off\n // the app base path prefix in case we're running the app on a sub-path.\n const relativeSourceLocation = this.trimPath(options?.sourcePath ?? '');\n\n // Next we figure out the base path, which is the combination of the common parent path\n // between our current location and our target location, as well as the additional path\n // that is the difference between the parent path and the base of our target location.\n const basePath = resolveBasePath(\n targetRef,\n relativeSourceLocation,\n this.routePaths,\n this.routeParents,\n this.routeObjects,\n );\n\n const routeFunc: RouteFunc<TParams> = (...[params]) => {\n // We selectively encode some some known-dangerous characters in the\n // params. The reason that we don't perform a blanket `encodeURIComponent`\n // here is that this encoding was added defensively long after the initial\n // release of this code. There's likely to be many users of this code that\n // already encode their parameters knowing that this code didn't do this\n // for them in the past. Therefore, we are extra careful NOT to include\n // the percent character in this set, even though that might seem like a\n // bad idea.\n const encodedParams =\n params &&\n mapValues(params, value => {\n if (typeof value === 'string') {\n return value.replaceAll(/[&?#;\\/]/g, c => encodeURIComponent(c));\n }\n return value;\n });\n return joinPaths(basePath, generatePath(targetPath, encodedParams));\n };\n return routeFunc;\n }\n\n private trimPath(targetPath: string) {\n if (!targetPath) {\n return targetPath;\n }\n\n if (targetPath.startsWith(this.appBasePath)) {\n return targetPath.slice(this.appBasePath.length);\n }\n return targetPath;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RouteResolver.esm.js","sources":["../../src/routing/RouteResolver.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 { generatePath, matchRoutes } from 'react-router-dom';\nimport {\n RouteRef,\n ExternalRouteRef,\n SubRouteRef,\n AnyRouteRefParams,\n RouteFunc,\n RouteResolutionApi,\n} from '@backstage/frontend-plugin-api';\nimport mapValues from 'lodash/mapValues';\nimport { AnyRouteRef, BackstageRouteObject } from './types';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { isRouteRef } from '../../../frontend-plugin-api/src/routing/RouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isSubRouteRef,\n toInternalSubRouteRef,\n} from '../../../frontend-plugin-api/src/routing/SubRouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { isExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef';\nimport { RouteAliasResolver } from './RouteAliasResolver';\n\n// Joins a list of paths together, avoiding trailing and duplicate slashes\nexport function joinPaths(...paths: string[]): string {\n const normalized = paths.join('/').replace(/\\/\\/+/g, '/');\n if (normalized !== '/' && normalized.endsWith('/')) {\n return normalized.slice(0, -1);\n }\n return normalized;\n}\n\n/**\n * Resolves the absolute route ref that our target route ref is pointing pointing to, as well\n * as the relative target path.\n *\n * Returns an undefined target ref if one could not be fully resolved.\n */\nfunction resolveTargetRef(\n anyRouteRef: AnyRouteRef,\n routePaths: Map<RouteRef, string>,\n routeBindings: Map<AnyRouteRef, AnyRouteRef | undefined>,\n): readonly [RouteRef | undefined, string] {\n // First we figure out which absolute route ref we're dealing with, an if there was an sub route path to append.\n // For sub routes it will be the parent path, while for external routes it will be the bound route.\n let targetRef: RouteRef;\n let subRoutePath = '';\n if (isRouteRef(anyRouteRef)) {\n targetRef = anyRouteRef;\n } else if (isSubRouteRef(anyRouteRef)) {\n const internal = toInternalSubRouteRef(anyRouteRef);\n targetRef = internal.getParent();\n subRoutePath = internal.path;\n } else if (isExternalRouteRef(anyRouteRef)) {\n const resolvedRoute = routeBindings.get(anyRouteRef);\n if (!resolvedRoute) {\n return [undefined, ''];\n }\n if (isRouteRef(resolvedRoute)) {\n targetRef = resolvedRoute;\n } else if (isSubRouteRef(resolvedRoute)) {\n const internal = toInternalSubRouteRef(resolvedRoute);\n targetRef = internal.getParent();\n subRoutePath = resolvedRoute.path;\n } else {\n throw new Error(\n `ExternalRouteRef was bound to invalid target, ${resolvedRoute}`,\n );\n }\n } else {\n throw new Error(`Unknown object passed to useRouteRef, got ${anyRouteRef}`);\n }\n\n // Bail if no absolute path could be resolved\n if (!targetRef) {\n return [undefined, ''];\n }\n\n // Find the path that our target route is bound to\n const resolvedPath = routePaths.get(targetRef);\n if (resolvedPath === undefined) {\n return [undefined, ''];\n }\n\n // SubRouteRefs join the path from the parent route with its own path\n const targetPath = joinPaths(resolvedPath, subRoutePath);\n return [targetRef, targetPath];\n}\n\n/**\n * Resolves the complete base path for navigating to the target RouteRef.\n */\nfunction resolveBasePath(\n targetRef: RouteRef,\n sourceLocation: Parameters<typeof matchRoutes>[1],\n routePaths: Map<RouteRef, string>,\n routeParents: Map<RouteRef, RouteRef | undefined>,\n routeObjects: BackstageRouteObject[],\n) {\n // While traversing the app element tree we build up the routeObjects structure\n // used here. It is the same kind of structure that react-router creates, with the\n // addition that associated route refs are stored throughout the tree. This lets\n // us look up all route refs that can be reached from our source location.\n // Because of the similar route object structure, we can use `matchRoutes` from\n // react-router to do the lookup of our current location.\n const match = matchRoutes(routeObjects, sourceLocation) ?? [];\n\n // While we search for a common routing root between our current location and\n // the target route, we build a list of all route refs we find that we need\n // to traverse to reach the target.\n const refDiffList = Array<RouteRef>();\n\n let matchIndex = -1;\n for (\n let targetSearchRef: RouteRef | undefined = targetRef;\n targetSearchRef;\n targetSearchRef = routeParents.get(targetSearchRef)\n ) {\n // The match contains a list of all ancestral route refs present at our current location\n // Starting at the desired target ref and traversing back through its parents, we search\n // for a target ref that is present in the match for our current location. When a match\n // is found it means we have found a common base to resolve the route from.\n matchIndex = match.findIndex(m =>\n (m.route as BackstageRouteObject).routeRefs.has(targetSearchRef!),\n );\n if (matchIndex !== -1) {\n break;\n }\n\n // Every time we move a step up in the ancestry of the target ref, we add the current ref\n // to the diff list, which ends up being the list of route refs to traverse form the common base\n // in order to reach our target.\n refDiffList.unshift(targetSearchRef);\n }\n\n // If our target route is present in the initial match we need to construct the final path\n // from the parent of the matched route segment. That's to allow the caller of the route\n // function to supply their own params.\n if (refDiffList.length === 0) {\n matchIndex -= 1;\n }\n\n // This is the part of the route tree that the target and source locations have in common.\n // We re-use the existing pathname directly along with all params.\n const parentPath = matchIndex === -1 ? '' : match[matchIndex].pathname;\n\n // This constructs the mid section of the path using paths resolved from all route refs\n // we need to traverse to reach our target except for the very last one. None of these\n // paths are allowed to require any parameters, as the caller would have no way of knowing\n // what parameters those are.\n const diffPaths = refDiffList.slice(0, -1).map(ref => {\n const path = routePaths.get(ref);\n if (path === undefined) {\n throw new Error(`No path for ${ref}`);\n }\n if (path.includes(':')) {\n throw new Error(\n `Cannot route to ${targetRef} with parent ${ref} as it has parameters`,\n );\n }\n return path;\n });\n\n return `${joinPaths(parentPath, ...diffPaths)}/`;\n}\n\nexport class RouteResolver implements RouteResolutionApi {\n constructor(\n private readonly routePaths: Map<RouteRef, string>,\n private readonly routeParents: Map<RouteRef, RouteRef | undefined>,\n private readonly routeObjects: BackstageRouteObject[],\n private readonly routeBindings: Map<\n ExternalRouteRef,\n RouteRef | SubRouteRef\n >,\n private readonly appBasePath: string, // base path without a trailing slash\n private readonly routeAliasResolver: RouteAliasResolver,\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 // First figure out what our target absolute ref is, as well as our target path.\n const [targetRef, targetPath] = resolveTargetRef(\n anyRouteRef?.$$type === '@backstage/RouteRef'\n ? this.routeAliasResolver(anyRouteRef)\n : anyRouteRef,\n this.routePaths,\n this.routeBindings,\n );\n if (!targetRef) {\n return undefined;\n }\n\n // The location that we get passed in uses the full path, so start by trimming off\n // the app base path prefix in case we're running the app on a sub-path.\n const relativeSourceLocation = this.trimPath(options?.sourcePath ?? '');\n\n // Next we figure out the base path, which is the combination of the common parent path\n // between our current location and our target location, as well as the additional path\n // that is the difference between the parent path and the base of our target location.\n const basePath = resolveBasePath(\n targetRef,\n relativeSourceLocation,\n this.routePaths,\n this.routeParents,\n this.routeObjects,\n );\n\n const routeFunc: RouteFunc<TParams> = (...[params]) => {\n // We selectively encode some some known-dangerous characters in the\n // params. The reason that we don't perform a blanket `encodeURIComponent`\n // here is that this encoding was added defensively long after the initial\n // release of this code. There's likely to be many users of this code that\n // already encode their parameters knowing that this code didn't do this\n // for them in the past. Therefore, we are extra careful NOT to include\n // the percent character in this set, even though that might seem like a\n // bad idea.\n const encodedParams =\n params &&\n mapValues(params, value => {\n if (typeof value === 'string') {\n return value.replaceAll(/[&?#;\\/]/g, c => encodeURIComponent(c));\n }\n return value;\n });\n return joinPaths(basePath, generatePath(targetPath, encodedParams));\n };\n return routeFunc;\n }\n\n private trimPath(targetPath: string) {\n if (!targetPath) {\n return targetPath;\n }\n\n if (targetPath.startsWith(this.appBasePath)) {\n return targetPath.slice(this.appBasePath.length);\n }\n return targetPath;\n }\n}\n"],"names":[],"mappings":";;;;;;AAuCO,SAAS,aAAa,KAAyB,EAAA;AACpD,EAAA,MAAM,aAAa,KAAM,CAAA,IAAA,CAAK,GAAG,CAAE,CAAA,OAAA,CAAQ,UAAU,GAAG,CAAA;AACxD,EAAA,IAAI,UAAe,KAAA,GAAA,IAAO,UAAW,CAAA,QAAA,CAAS,GAAG,CAAG,EAAA;AAClD,IAAO,OAAA,UAAA,CAAW,KAAM,CAAA,CAAA,EAAG,CAAE,CAAA,CAAA;AAAA;AAE/B,EAAO,OAAA,UAAA;AACT;AAQA,SAAS,gBAAA,CACP,WACA,EAAA,UAAA,EACA,aACyC,EAAA;AAGzC,EAAI,IAAA,SAAA;AACJ,EAAA,IAAI,YAAe,GAAA,EAAA;AACnB,EAAI,IAAA,UAAA,CAAW,WAAW,CAAG,EAAA;AAC3B,IAAY,SAAA,GAAA,WAAA;AAAA,GACd,MAAA,IAAW,aAAc,CAAA,WAAW,CAAG,EAAA;AACrC,IAAM,MAAA,QAAA,GAAW,sBAAsB,WAAW,CAAA;AAClD,IAAA,SAAA,GAAY,SAAS,SAAU,EAAA;AAC/B,IAAA,YAAA,GAAe,QAAS,CAAA,IAAA;AAAA,GAC1B,MAAA,IAAW,kBAAmB,CAAA,WAAW,CAAG,EAAA;AAC1C,IAAM,MAAA,aAAA,GAAgB,aAAc,CAAA,GAAA,CAAI,WAAW,CAAA;AACnD,IAAA,IAAI,CAAC,aAAe,EAAA;AAClB,MAAO,OAAA,CAAC,QAAW,EAAE,CAAA;AAAA;AAEvB,IAAI,IAAA,UAAA,CAAW,aAAa,CAAG,EAAA;AAC7B,MAAY,SAAA,GAAA,aAAA;AAAA,KACd,MAAA,IAAW,aAAc,CAAA,aAAa,CAAG,EAAA;AACvC,MAAM,MAAA,QAAA,GAAW,sBAAsB,aAAa,CAAA;AACpD,MAAA,SAAA,GAAY,SAAS,SAAU,EAAA;AAC/B,MAAA,YAAA,GAAe,aAAc,CAAA,IAAA;AAAA,KACxB,MAAA;AACL,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,iDAAiD,aAAa,CAAA;AAAA,OAChE;AAAA;AACF,GACK,MAAA;AACL,IAAA,MAAM,IAAI,KAAA,CAAM,CAA6C,0CAAA,EAAA,WAAW,CAAE,CAAA,CAAA;AAAA;AAI5E,EAAA,IAAI,CAAC,SAAW,EAAA;AACd,IAAO,OAAA,CAAC,QAAW,EAAE,CAAA;AAAA;AAIvB,EAAM,MAAA,YAAA,GAAe,UAAW,CAAA,GAAA,CAAI,SAAS,CAAA;AAC7C,EAAA,IAAI,iBAAiB,KAAW,CAAA,EAAA;AAC9B,IAAO,OAAA,CAAC,QAAW,EAAE,CAAA;AAAA;AAIvB,EAAM,MAAA,UAAA,GAAa,SAAU,CAAA,YAAA,EAAc,YAAY,CAAA;AACvD,EAAO,OAAA,CAAC,WAAW,UAAU,CAAA;AAC/B;AAKA,SAAS,eACP,CAAA,SAAA,EACA,cACA,EAAA,UAAA,EACA,cACA,YACA,EAAA;AAOA,EAAA,MAAM,KAAQ,GAAA,WAAA,CAAY,YAAc,EAAA,cAAc,KAAK,EAAC;AAK5D,EAAA,MAAM,cAAc,KAAgB,EAAA;AAEpC,EAAA,IAAI,UAAa,GAAA,CAAA,CAAA;AACjB,EAAA,KAAA,IACM,kBAAwC,SAC5C,EAAA,eAAA,EACA,kBAAkB,YAAa,CAAA,GAAA,CAAI,eAAe,CAClD,EAAA;AAKA,IAAA,UAAA,GAAa,KAAM,CAAA,SAAA;AAAA,MAAU,CAC1B,CAAA,KAAA,CAAA,CAAE,KAA+B,CAAA,SAAA,CAAU,IAAI,eAAgB;AAAA,KAClE;AACA,IAAA,IAAI,eAAe,CAAI,CAAA,EAAA;AACrB,MAAA;AAAA;AAMF,IAAA,WAAA,CAAY,QAAQ,eAAe,CAAA;AAAA;AAMrC,EAAI,IAAA,WAAA,CAAY,WAAW,CAAG,EAAA;AAC5B,IAAc,UAAA,IAAA,CAAA;AAAA;AAKhB,EAAA,MAAM,aAAa,UAAe,KAAA,CAAA,CAAA,GAAK,EAAK,GAAA,KAAA,CAAM,UAAU,CAAE,CAAA,QAAA;AAM9D,EAAA,MAAM,YAAY,WAAY,CAAA,KAAA,CAAM,GAAG,CAAE,CAAA,CAAA,CAAE,IAAI,CAAO,GAAA,KAAA;AACpD,IAAM,MAAA,IAAA,GAAO,UAAW,CAAA,GAAA,CAAI,GAAG,CAAA;AAC/B,IAAA,IAAI,SAAS,KAAW,CAAA,EAAA;AACtB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAe,YAAA,EAAA,GAAG,CAAE,CAAA,CAAA;AAAA;AAEtC,IAAI,IAAA,IAAA,CAAK,QAAS,CAAA,GAAG,CAAG,EAAA;AACtB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,gBAAA,EAAmB,SAAS,CAAA,aAAA,EAAgB,GAAG,CAAA,qBAAA;AAAA,OACjD;AAAA;AAEF,IAAO,OAAA,IAAA;AAAA,GACR,CAAA;AAED,EAAA,OAAO,CAAG,EAAA,SAAA,CAAU,UAAY,EAAA,GAAG,SAAS,CAAC,CAAA,CAAA,CAAA;AAC/C;AAEO,MAAM,aAA4C,CAAA;AAAA,EACvD,YACmB,UACA,EAAA,YAAA,EACA,YACA,EAAA,aAAA,EAIA,aACA,kBACjB,EAAA;AATiB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AACA,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AACA,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AAIA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,kBAAA,GAAA,kBAAA;AAAA;AAChB,EAEH,OAAA,CACE,aAIA,OACgC,EAAA;AAEhC,IAAM,MAAA,CAAC,SAAW,EAAA,UAAU,CAAI,GAAA,gBAAA;AAAA,MAC9B,aAAa,MAAW,KAAA,qBAAA,GACpB,IAAK,CAAA,kBAAA,CAAmB,WAAW,CACnC,GAAA,WAAA;AAAA,MACJ,IAAK,CAAA,UAAA;AAAA,MACL,IAAK,CAAA;AAAA,KACP;AACA,IAAA,IAAI,CAAC,SAAW,EAAA;AACd,MAAO,OAAA,KAAA,CAAA;AAAA;AAKT,IAAA,MAAM,sBAAyB,GAAA,IAAA,CAAK,QAAS,CAAA,OAAA,EAAS,cAAc,EAAE,CAAA;AAKtE,IAAA,MAAM,QAAW,GAAA,eAAA;AAAA,MACf,SAAA;AAAA,MACA,sBAAA;AAAA,MACA,IAAK,CAAA,UAAA;AAAA,MACL,IAAK,CAAA,YAAA;AAAA,MACL,IAAK,CAAA;AAAA,KACP;AAEA,IAAA,MAAM,SAAgC,GAAA,CAAA,GAAI,CAAC,MAAM,CAAM,KAAA;AASrD,MAAA,MAAM,aACJ,GAAA,MAAA,IACA,SAAU,CAAA,MAAA,EAAQ,CAAS,KAAA,KAAA;AACzB,QAAI,IAAA,OAAO,UAAU,QAAU,EAAA;AAC7B,UAAA,OAAO,MAAM,UAAW,CAAA,WAAA,EAAa,CAAK,CAAA,KAAA,kBAAA,CAAmB,CAAC,CAAC,CAAA;AAAA;AAEjE,QAAO,OAAA,KAAA;AAAA,OACR,CAAA;AACH,MAAA,OAAO,SAAU,CAAA,QAAA,EAAU,YAAa,CAAA,UAAA,EAAY,aAAa,CAAC,CAAA;AAAA,KACpE;AACA,IAAO,OAAA,SAAA;AAAA;AACT,EAEQ,SAAS,UAAoB,EAAA;AACnC,IAAA,IAAI,CAAC,UAAY,EAAA;AACf,MAAO,OAAA,UAAA;AAAA;AAGT,IAAA,IAAI,UAAW,CAAA,UAAA,CAAW,IAAK,CAAA,WAAW,CAAG,EAAA;AAC3C,MAAA,OAAO,UAAW,CAAA,KAAA,CAAM,IAAK,CAAA,WAAA,CAAY,MAAM,CAAA;AAAA;AAEjD,IAAO,OAAA,UAAA;AAAA;AAEX;;;;"}
@@ -2,6 +2,7 @@ import { isRouteRef, toInternalRouteRef } from '../frontend-plugin-api/src/routi
2
2
  import { toInternalExternalRouteRef } from '../frontend-plugin-api/src/routing/ExternalRouteRef.esm.js';
3
3
  import { toInternalSubRouteRef } from '../frontend-plugin-api/src/routing/SubRouteRef.esm.js';
4
4
  import { OpaqueFrontendPlugin } from '../frontend-internal/src/wiring/InternalFrontendPlugin.esm.js';
5
+ import '../frontend-internal/src/wiring/InternalSwappableComponentRef.esm.js';
5
6
  import '../frontend-internal/src/wiring/InternalExtensionDefinition.esm.js';
6
7
 
7
8
  function collectRouteIds(features) {
@@ -1 +1 @@
1
- {"version":3,"file":"collectRouteIds.esm.js","sources":["../../src/routing/collectRouteIds.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 RouteRef,\n SubRouteRef,\n ExternalRouteRef,\n} from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isRouteRef,\n toInternalRouteRef,\n} from '../../../frontend-plugin-api/src/routing/RouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef';\nimport { OpaqueFrontendPlugin } from '@internal/frontend';\nimport { FrontendFeature } from '../wiring/types';\n\n/** @internal */\nexport interface RouteRefsById {\n routes: Map<string, RouteRef | SubRouteRef>;\n externalRoutes: Map<string, ExternalRouteRef>;\n}\n\n/** @internal */\nexport function collectRouteIds(features: FrontendFeature[]): RouteRefsById {\n const routesById = new Map<string, RouteRef | SubRouteRef>();\n const externalRoutesById = new Map<string, ExternalRouteRef>();\n\n for (const feature of features) {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n continue;\n }\n\n for (const [name, ref] of Object.entries(feature.routes)) {\n const refId = `${feature.id}.${name}`;\n if (routesById.has(refId)) {\n throw new Error(`Unexpected duplicate route '${refId}'`);\n }\n\n if (isRouteRef(ref)) {\n const internalRef = toInternalRouteRef(ref);\n internalRef.setId(refId);\n routesById.set(refId, ref);\n } else {\n const internalRef = toInternalSubRouteRef(ref);\n routesById.set(refId, internalRef);\n }\n }\n for (const [name, ref] of Object.entries(feature.externalRoutes)) {\n const refId = `${feature.id}.${name}`;\n if (externalRoutesById.has(refId)) {\n throw new Error(`Unexpected duplicate external route '${refId}'`);\n }\n\n const internalRef = toInternalExternalRouteRef(ref);\n internalRef.setId(refId);\n externalRoutesById.set(refId, ref);\n }\n }\n\n return { routes: routesById, externalRoutes: externalRoutesById };\n}\n"],"names":[],"mappings":";;;;;;AAwCO,SAAS,gBAAgB,QAA4C,EAAA;AAC1E,EAAM,MAAA,UAAA,uBAAiB,GAAoC,EAAA;AAC3D,EAAM,MAAA,kBAAA,uBAAyB,GAA8B,EAAA;AAE7D,EAAA,KAAA,MAAW,WAAW,QAAU,EAAA;AAC9B,IAAA,IAAI,CAAC,oBAAA,CAAqB,MAAO,CAAA,OAAO,CAAG,EAAA;AACzC,MAAA;AAAA;AAGF,IAAW,KAAA,MAAA,CAAC,MAAM,GAAG,CAAA,IAAK,OAAO,OAAQ,CAAA,OAAA,CAAQ,MAAM,CAAG,EAAA;AACxD,MAAA,MAAM,KAAQ,GAAA,CAAA,EAAG,OAAQ,CAAA,EAAE,IAAI,IAAI,CAAA,CAAA;AACnC,MAAI,IAAA,UAAA,CAAW,GAAI,CAAA,KAAK,CAAG,EAAA;AACzB,QAAA,MAAM,IAAI,KAAA,CAAM,CAA+B,4BAAA,EAAA,KAAK,CAAG,CAAA,CAAA,CAAA;AAAA;AAGzD,MAAI,IAAA,UAAA,CAAW,GAAG,CAAG,EAAA;AACnB,QAAM,MAAA,WAAA,GAAc,mBAAmB,GAAG,CAAA;AAC1C,QAAA,WAAA,CAAY,MAAM,KAAK,CAAA;AACvB,QAAW,UAAA,CAAA,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,OACpB,MAAA;AACL,QAAM,MAAA,WAAA,GAAc,sBAAsB,GAAG,CAAA;AAC7C,QAAW,UAAA,CAAA,GAAA,CAAI,OAAO,WAAW,CAAA;AAAA;AACnC;AAEF,IAAW,KAAA,MAAA,CAAC,MAAM,GAAG,CAAA,IAAK,OAAO,OAAQ,CAAA,OAAA,CAAQ,cAAc,CAAG,EAAA;AAChE,MAAA,MAAM,KAAQ,GAAA,CAAA,EAAG,OAAQ,CAAA,EAAE,IAAI,IAAI,CAAA,CAAA;AACnC,MAAI,IAAA,kBAAA,CAAmB,GAAI,CAAA,KAAK,CAAG,EAAA;AACjC,QAAA,MAAM,IAAI,KAAA,CAAM,CAAwC,qCAAA,EAAA,KAAK,CAAG,CAAA,CAAA,CAAA;AAAA;AAGlE,MAAM,MAAA,WAAA,GAAc,2BAA2B,GAAG,CAAA;AAClD,MAAA,WAAA,CAAY,MAAM,KAAK,CAAA;AACvB,MAAmB,kBAAA,CAAA,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA;AACnC;AAGF,EAAA,OAAO,EAAE,MAAA,EAAQ,UAAY,EAAA,cAAA,EAAgB,kBAAmB,EAAA;AAClE;;;;"}
1
+ {"version":3,"file":"collectRouteIds.esm.js","sources":["../../src/routing/collectRouteIds.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 RouteRef,\n SubRouteRef,\n ExternalRouteRef,\n FrontendFeature,\n} from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isRouteRef,\n toInternalRouteRef,\n} from '../../../frontend-plugin-api/src/routing/RouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef';\nimport { OpaqueFrontendPlugin } from '@internal/frontend';\n\n/** @internal */\nexport interface RouteRefsById {\n routes: Map<string, RouteRef | SubRouteRef>;\n externalRoutes: Map<string, ExternalRouteRef>;\n}\n\n/** @internal */\nexport function collectRouteIds(features: FrontendFeature[]): RouteRefsById {\n const routesById = new Map<string, RouteRef | SubRouteRef>();\n const externalRoutesById = new Map<string, ExternalRouteRef>();\n\n for (const feature of features) {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n continue;\n }\n\n for (const [name, ref] of Object.entries(feature.routes)) {\n const refId = `${feature.id}.${name}`;\n if (routesById.has(refId)) {\n throw new Error(`Unexpected duplicate route '${refId}'`);\n }\n\n if (isRouteRef(ref)) {\n const internalRef = toInternalRouteRef(ref);\n internalRef.setId(refId);\n routesById.set(refId, ref);\n } else {\n const internalRef = toInternalSubRouteRef(ref);\n routesById.set(refId, internalRef);\n }\n }\n for (const [name, ref] of Object.entries(feature.externalRoutes)) {\n const refId = `${feature.id}.${name}`;\n if (externalRoutesById.has(refId)) {\n throw new Error(`Unexpected duplicate external route '${refId}'`);\n }\n\n const internalRef = toInternalExternalRouteRef(ref);\n internalRef.setId(refId);\n externalRoutesById.set(refId, ref);\n }\n }\n\n return { routes: routesById, externalRoutes: externalRoutesById };\n}\n"],"names":[],"mappings":";;;;;;;AAwCO,SAAS,gBAAgB,QAA4C,EAAA;AAC1E,EAAM,MAAA,UAAA,uBAAiB,GAAoC,EAAA;AAC3D,EAAM,MAAA,kBAAA,uBAAyB,GAA8B,EAAA;AAE7D,EAAA,KAAA,MAAW,WAAW,QAAU,EAAA;AAC9B,IAAA,IAAI,CAAC,oBAAA,CAAqB,MAAO,CAAA,OAAO,CAAG,EAAA;AACzC,MAAA;AAAA;AAGF,IAAW,KAAA,MAAA,CAAC,MAAM,GAAG,CAAA,IAAK,OAAO,OAAQ,CAAA,OAAA,CAAQ,MAAM,CAAG,EAAA;AACxD,MAAA,MAAM,KAAQ,GAAA,CAAA,EAAG,OAAQ,CAAA,EAAE,IAAI,IAAI,CAAA,CAAA;AACnC,MAAI,IAAA,UAAA,CAAW,GAAI,CAAA,KAAK,CAAG,EAAA;AACzB,QAAA,MAAM,IAAI,KAAA,CAAM,CAA+B,4BAAA,EAAA,KAAK,CAAG,CAAA,CAAA,CAAA;AAAA;AAGzD,MAAI,IAAA,UAAA,CAAW,GAAG,CAAG,EAAA;AACnB,QAAM,MAAA,WAAA,GAAc,mBAAmB,GAAG,CAAA;AAC1C,QAAA,WAAA,CAAY,MAAM,KAAK,CAAA;AACvB,QAAW,UAAA,CAAA,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,OACpB,MAAA;AACL,QAAM,MAAA,WAAA,GAAc,sBAAsB,GAAG,CAAA;AAC7C,QAAW,UAAA,CAAA,GAAA,CAAI,OAAO,WAAW,CAAA;AAAA;AACnC;AAEF,IAAW,KAAA,MAAA,CAAC,MAAM,GAAG,CAAA,IAAK,OAAO,OAAQ,CAAA,OAAA,CAAQ,cAAc,CAAG,EAAA;AAChE,MAAA,MAAM,KAAQ,GAAA,CAAA,EAAG,OAAQ,CAAA,EAAE,IAAI,IAAI,CAAA,CAAA;AACnC,MAAI,IAAA,kBAAA,CAAmB,GAAI,CAAA,KAAK,CAAG,EAAA;AACjC,QAAA,MAAM,IAAI,KAAA,CAAM,CAAwC,qCAAA,EAAA,KAAK,CAAG,CAAA,CAAA,CAAA;AAAA;AAGlE,MAAM,MAAA,WAAA,GAAc,2BAA2B,GAAG,CAAA;AAClD,MAAA,WAAA,CAAY,MAAM,KAAK,CAAA;AACvB,MAAmB,kBAAA,CAAA,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA;AACnC;AAGF,EAAA,OAAO,EAAE,MAAA,EAAQ,UAAY,EAAA,cAAA,EAAgB,kBAAmB,EAAA;AAClE;;;;"}
@@ -1,5 +1,6 @@
1
1
  import { coreExtensionData } from '@backstage/frontend-plugin-api';
2
2
  import { toLegacyPlugin } from './toLegacyPlugin.esm.js';
3
+ import { createExactRouteAliasResolver } from './RouteAliasResolver.esm.js';
3
4
 
4
5
  const MATCH_ALL_ROUTE = {
5
6
  caseSensitive: false,
@@ -16,13 +17,18 @@ function joinPaths(...paths) {
16
17
  }
17
18
  return normalized;
18
19
  }
19
- function extractRouteInfoFromAppNode(node) {
20
+ function extractRouteInfoFromAppNode(node, routeAliasResolver) {
20
21
  const routePaths = /* @__PURE__ */ new Map();
21
22
  const routeParents = /* @__PURE__ */ new Map();
22
23
  const routeObjects = new Array();
24
+ const routeAliases = /* @__PURE__ */ new Map();
23
25
  function visit(current, collectedPath, foundRefForCollectedPath = false, parentRef, candidateParentRef, parentObj) {
24
26
  const routePath = current.instance?.getData(coreExtensionData.routePath)?.replace(/^\//, "");
25
- const routeRef = current.instance?.getData(coreExtensionData.routeRef);
27
+ const foundRouteRef = current.instance?.getData(coreExtensionData.routeRef);
28
+ const routeRef = routeAliasResolver(foundRouteRef, current.spec.plugin?.id);
29
+ if (foundRouteRef && routeRef !== foundRouteRef) {
30
+ routeAliases.set(foundRouteRef, routeRef);
31
+ }
26
32
  const parentChildren = parentObj?.children ?? routeObjects;
27
33
  let currentObj = parentObj;
28
34
  let newCollectedPath = collectedPath;
@@ -77,7 +83,12 @@ function extractRouteInfoFromAppNode(node) {
77
83
  }
78
84
  }
79
85
  visit(node);
80
- return { routePaths, routeParents, routeObjects };
86
+ return {
87
+ routePaths,
88
+ routeParents,
89
+ routeObjects,
90
+ routeAliasResolver: createExactRouteAliasResolver(routeAliases)
91
+ };
81
92
  }
82
93
 
83
94
  export { MATCH_ALL_ROUTE, extractRouteInfoFromAppNode, joinPaths };
@@ -1 +1 @@
1
- {"version":3,"file":"extractRouteInfoFromAppNode.esm.js","sources":["../../src/routing/extractRouteInfoFromAppNode.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 { RouteRef, coreExtensionData } from '@backstage/frontend-plugin-api';\nimport { BackstageRouteObject } from './types';\nimport { AppNode } from '@backstage/frontend-plugin-api';\nimport { toLegacyPlugin } from './toLegacyPlugin';\n\n// We always add a child that matches all subroutes but without any route refs. This makes\n// sure that we're always able to match each route no matter how deep the navigation goes.\n// The route resolver then takes care of selecting the most specific match in order to find\n// mount points that are as deep in the routing tree as possible.\nexport const MATCH_ALL_ROUTE: BackstageRouteObject = {\n caseSensitive: false,\n path: '*',\n element: 'match-all', // These elements aren't used, so we add in a bit of debug information\n routeRefs: new Set(),\n plugins: new Set(),\n};\n\n// Joins a list of paths together, avoiding trailing and duplicate slashes\nexport function joinPaths(...paths: string[]): string {\n const normalized = paths.join('/').replace(/\\/\\/+/g, '/');\n if (normalized !== '/' && normalized.endsWith('/')) {\n return normalized.slice(0, -1);\n }\n return normalized;\n}\n\nexport function extractRouteInfoFromAppNode(node: AppNode): {\n routePaths: Map<RouteRef, string>;\n routeParents: Map<RouteRef, RouteRef | undefined>;\n routeObjects: BackstageRouteObject[];\n} {\n // This tracks the route path for each route ref, the value is the route path relative to the parent ref\n const routePaths = new Map<RouteRef, string>();\n // This tracks the parents of each route ref. To find the full path of any route ref you traverse\n // upwards in this tree and substitute each route ref for its route path along then way.\n const routeParents = new Map<RouteRef, RouteRef | undefined>();\n // This route object tree is passed to react-router in order to be able to look up the current route\n // ref or extension/source based on our current location.\n const routeObjects = new Array<BackstageRouteObject>();\n\n function visit(\n current: AppNode,\n collectedPath?: string,\n foundRefForCollectedPath: boolean = false,\n parentRef?: RouteRef,\n candidateParentRef?: RouteRef,\n parentObj?: BackstageRouteObject,\n ) {\n const routePath = current.instance\n ?.getData(coreExtensionData.routePath)\n ?.replace(/^\\//, '');\n const routeRef = current.instance?.getData(coreExtensionData.routeRef);\n const parentChildren = parentObj?.children ?? routeObjects;\n let currentObj = parentObj;\n\n let newCollectedPath = collectedPath;\n let newFoundRefForCollectedPath = foundRefForCollectedPath;\n\n let newParentRef = parentRef;\n let newCandidateParentRef = candidateParentRef;\n\n // Whenever a route path is encountered, a new node is created in the routing tree.\n if (routePath !== undefined) {\n currentObj = {\n path: routePath,\n element: 'mounted',\n routeRefs: new Set<RouteRef>(),\n caseSensitive: false,\n children: [MATCH_ALL_ROUTE],\n plugins: new Set(),\n appNode: current,\n };\n parentChildren.push(currentObj);\n\n // Each route path that we discover creates a new node in the routing tree, at that point\n // we also switch out our candidate parent ref to be the active one.\n newParentRef = candidateParentRef;\n newCandidateParentRef = undefined;\n\n // We need to collect and concatenate route paths until the path has been assigned a route ref:\n // Once we find a route ref the collection starts over from an empty path, that way each route\n // path assignment only contains the diff from the parent ref.\n if (newFoundRefForCollectedPath) {\n newCollectedPath = routePath;\n newFoundRefForCollectedPath = false;\n } else {\n newCollectedPath = collectedPath\n ? joinPaths(collectedPath, routePath)\n : routePath;\n }\n }\n\n // Whenever a route ref is encountered, we need to give it a route path and position in the ref tree.\n if (routeRef) {\n // The first route ref we find after encountering a route path is selected to be used as the\n // parent ref further down the tree. We don't start using this candidate ref until we encounter\n // another route path though, at which point we repeat the process and select another candidate.\n if (!newCandidateParentRef) {\n newCandidateParentRef = routeRef;\n }\n\n // Check if we've encountered any route paths since the closest route ref, in that case we assign\n // that path to this and following route refs until we encounter another route path.\n if (newCollectedPath !== undefined) {\n routePaths.set(routeRef, newCollectedPath);\n newFoundRefForCollectedPath = true;\n }\n\n routeParents.set(routeRef, newParentRef);\n currentObj?.routeRefs.add(routeRef);\n if (current.spec.plugin) {\n currentObj?.plugins.add(toLegacyPlugin(current.spec.plugin));\n }\n }\n\n for (const children of current.edges.attachments.values()) {\n for (const child of children) {\n visit(\n child,\n newCollectedPath,\n newFoundRefForCollectedPath,\n newParentRef,\n newCandidateParentRef,\n currentObj,\n );\n }\n }\n }\n\n visit(node);\n\n return { routePaths, routeParents, routeObjects };\n}\n"],"names":[],"mappings":";;;AAyBO,MAAM,eAAwC,GAAA;AAAA,EACnD,aAAe,EAAA,KAAA;AAAA,EACf,IAAM,EAAA,GAAA;AAAA,EACN,OAAS,EAAA,WAAA;AAAA;AAAA,EACT,SAAA,sBAAe,GAAI,EAAA;AAAA,EACnB,OAAA,sBAAa,GAAI;AACnB;AAGO,SAAS,aAAa,KAAyB,EAAA;AACpD,EAAA,MAAM,aAAa,KAAM,CAAA,IAAA,CAAK,GAAG,CAAE,CAAA,OAAA,CAAQ,UAAU,GAAG,CAAA;AACxD,EAAA,IAAI,UAAe,KAAA,GAAA,IAAO,UAAW,CAAA,QAAA,CAAS,GAAG,CAAG,EAAA;AAClD,IAAO,OAAA,UAAA,CAAW,KAAM,CAAA,CAAA,EAAG,CAAE,CAAA,CAAA;AAAA;AAE/B,EAAO,OAAA,UAAA;AACT;AAEO,SAAS,4BAA4B,IAI1C,EAAA;AAEA,EAAM,MAAA,UAAA,uBAAiB,GAAsB,EAAA;AAG7C,EAAM,MAAA,YAAA,uBAAmB,GAAoC,EAAA;AAG7D,EAAM,MAAA,YAAA,GAAe,IAAI,KAA4B,EAAA;AAErD,EAAA,SAAS,MACP,OACA,EAAA,aAAA,EACA,2BAAoC,KACpC,EAAA,SAAA,EACA,oBACA,SACA,EAAA;AACA,IAAM,MAAA,SAAA,GAAY,QAAQ,QACtB,EAAA,OAAA,CAAQ,kBAAkB,SAAS,CAAA,EACnC,OAAQ,CAAA,KAAA,EAAO,EAAE,CAAA;AACrB,IAAA,MAAM,QAAW,GAAA,OAAA,CAAQ,QAAU,EAAA,OAAA,CAAQ,kBAAkB,QAAQ,CAAA;AACrE,IAAM,MAAA,cAAA,GAAiB,WAAW,QAAY,IAAA,YAAA;AAC9C,IAAA,IAAI,UAAa,GAAA,SAAA;AAEjB,IAAA,IAAI,gBAAmB,GAAA,aAAA;AACvB,IAAA,IAAI,2BAA8B,GAAA,wBAAA;AAElC,IAAA,IAAI,YAAe,GAAA,SAAA;AACnB,IAAA,IAAI,qBAAwB,GAAA,kBAAA;AAG5B,IAAA,IAAI,cAAc,KAAW,CAAA,EAAA;AAC3B,MAAa,UAAA,GAAA;AAAA,QACX,IAAM,EAAA,SAAA;AAAA,QACN,OAAS,EAAA,SAAA;AAAA,QACT,SAAA,sBAAe,GAAc,EAAA;AAAA,QAC7B,aAAe,EAAA,KAAA;AAAA,QACf,QAAA,EAAU,CAAC,eAAe,CAAA;AAAA,QAC1B,OAAA,sBAAa,GAAI,EAAA;AAAA,QACjB,OAAS,EAAA;AAAA,OACX;AACA,MAAA,cAAA,CAAe,KAAK,UAAU,CAAA;AAI9B,MAAe,YAAA,GAAA,kBAAA;AACf,MAAwB,qBAAA,GAAA,KAAA,CAAA;AAKxB,MAAA,IAAI,2BAA6B,EAAA;AAC/B,QAAmB,gBAAA,GAAA,SAAA;AACnB,QAA8B,2BAAA,GAAA,KAAA;AAAA,OACzB,MAAA;AACL,QAAA,gBAAA,GAAmB,aACf,GAAA,SAAA,CAAU,aAAe,EAAA,SAAS,CAClC,GAAA,SAAA;AAAA;AACN;AAIF,IAAA,IAAI,QAAU,EAAA;AAIZ,MAAA,IAAI,CAAC,qBAAuB,EAAA;AAC1B,QAAwB,qBAAA,GAAA,QAAA;AAAA;AAK1B,MAAA,IAAI,qBAAqB,KAAW,CAAA,EAAA;AAClC,QAAW,UAAA,CAAA,GAAA,CAAI,UAAU,gBAAgB,CAAA;AACzC,QAA8B,2BAAA,GAAA,IAAA;AAAA;AAGhC,MAAa,YAAA,CAAA,GAAA,CAAI,UAAU,YAAY,CAAA;AACvC,MAAY,UAAA,EAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AAClC,MAAI,IAAA,OAAA,CAAQ,KAAK,MAAQ,EAAA;AACvB,QAAA,UAAA,EAAY,QAAQ,GAAI,CAAA,cAAA,CAAe,OAAQ,CAAA,IAAA,CAAK,MAAM,CAAC,CAAA;AAAA;AAC7D;AAGF,IAAA,KAAA,MAAW,QAAY,IAAA,OAAA,CAAQ,KAAM,CAAA,WAAA,CAAY,QAAU,EAAA;AACzD,MAAA,KAAA,MAAW,SAAS,QAAU,EAAA;AAC5B,QAAA,KAAA;AAAA,UACE,KAAA;AAAA,UACA,gBAAA;AAAA,UACA,2BAAA;AAAA,UACA,YAAA;AAAA,UACA,qBAAA;AAAA,UACA;AAAA,SACF;AAAA;AACF;AACF;AAGF,EAAA,KAAA,CAAM,IAAI,CAAA;AAEV,EAAO,OAAA,EAAE,UAAY,EAAA,YAAA,EAAc,YAAa,EAAA;AAClD;;;;"}
1
+ {"version":3,"file":"extractRouteInfoFromAppNode.esm.js","sources":["../../src/routing/extractRouteInfoFromAppNode.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 { RouteRef, coreExtensionData } from '@backstage/frontend-plugin-api';\nimport { BackstageRouteObject } from './types';\nimport { AppNode } from '@backstage/frontend-plugin-api';\nimport { toLegacyPlugin } from './toLegacyPlugin';\nimport {\n createExactRouteAliasResolver,\n RouteAliasResolver,\n} from './RouteAliasResolver';\n\n// We always add a child that matches all subroutes but without any route refs. This makes\n// sure that we're always able to match each route no matter how deep the navigation goes.\n// The route resolver then takes care of selecting the most specific match in order to find\n// mount points that are as deep in the routing tree as possible.\nexport const MATCH_ALL_ROUTE: BackstageRouteObject = {\n caseSensitive: false,\n path: '*',\n element: 'match-all', // These elements aren't used, so we add in a bit of debug information\n routeRefs: new Set(),\n plugins: new Set(),\n};\n\n// Joins a list of paths together, avoiding trailing and duplicate slashes\nexport function joinPaths(...paths: string[]): string {\n const normalized = paths.join('/').replace(/\\/\\/+/g, '/');\n if (normalized !== '/' && normalized.endsWith('/')) {\n return normalized.slice(0, -1);\n }\n return normalized;\n}\n\nexport function extractRouteInfoFromAppNode(\n node: AppNode,\n routeAliasResolver: RouteAliasResolver,\n): {\n routePaths: Map<RouteRef, string>;\n routeParents: Map<RouteRef, RouteRef | undefined>;\n routeObjects: BackstageRouteObject[];\n routeAliasResolver: RouteAliasResolver;\n} {\n // This tracks the route path for each route ref, the value is the route path relative to the parent ref\n const routePaths = new Map<RouteRef, string>();\n // This tracks the parents of each route ref. To find the full path of any route ref you traverse\n // upwards in this tree and substitute each route ref for its route path along then way.\n const routeParents = new Map<RouteRef, RouteRef | undefined>();\n // This route object tree is passed to react-router in order to be able to look up the current route\n // ref or extension/source based on our current location.\n const routeObjects = new Array<BackstageRouteObject>();\n // This tracks all resolved route aliases. By storing and re-using the resolutions here we make sure that it's not\n // possible to pass an aliased route ref directly to the resolver, e.g. `useRouteRef(createRouteRef({ aliasFor: 'example.root' }))`\n const routeAliases = new Map<RouteRef, RouteRef | undefined>();\n\n function visit(\n current: AppNode,\n collectedPath?: string,\n foundRefForCollectedPath: boolean = false,\n parentRef?: RouteRef,\n candidateParentRef?: RouteRef,\n parentObj?: BackstageRouteObject,\n ) {\n const routePath = current.instance\n ?.getData(coreExtensionData.routePath)\n ?.replace(/^\\//, '');\n\n const foundRouteRef = current.instance?.getData(coreExtensionData.routeRef);\n const routeRef = routeAliasResolver(foundRouteRef, current.spec.plugin?.id);\n if (foundRouteRef && routeRef !== foundRouteRef) {\n routeAliases.set(foundRouteRef, routeRef);\n }\n\n const parentChildren = parentObj?.children ?? routeObjects;\n let currentObj = parentObj;\n\n let newCollectedPath = collectedPath;\n let newFoundRefForCollectedPath = foundRefForCollectedPath;\n\n let newParentRef = parentRef;\n let newCandidateParentRef = candidateParentRef;\n\n // Whenever a route path is encountered, a new node is created in the routing tree.\n if (routePath !== undefined) {\n currentObj = {\n path: routePath,\n element: 'mounted',\n routeRefs: new Set<RouteRef>(),\n caseSensitive: false,\n children: [MATCH_ALL_ROUTE],\n plugins: new Set(),\n appNode: current,\n };\n parentChildren.push(currentObj);\n\n // Each route path that we discover creates a new node in the routing tree, at that point\n // we also switch out our candidate parent ref to be the active one.\n newParentRef = candidateParentRef;\n newCandidateParentRef = undefined;\n\n // We need to collect and concatenate route paths until the path has been assigned a route ref:\n // Once we find a route ref the collection starts over from an empty path, that way each route\n // path assignment only contains the diff from the parent ref.\n if (newFoundRefForCollectedPath) {\n newCollectedPath = routePath;\n newFoundRefForCollectedPath = false;\n } else {\n newCollectedPath = collectedPath\n ? joinPaths(collectedPath, routePath)\n : routePath;\n }\n }\n\n // Whenever a route ref is encountered, we need to give it a route path and position in the ref tree.\n if (routeRef) {\n // The first route ref we find after encountering a route path is selected to be used as the\n // parent ref further down the tree. We don't start using this candidate ref until we encounter\n // another route path though, at which point we repeat the process and select another candidate.\n if (!newCandidateParentRef) {\n newCandidateParentRef = routeRef;\n }\n\n // Check if we've encountered any route paths since the closest route ref, in that case we assign\n // that path to this and following route refs until we encounter another route path.\n if (newCollectedPath !== undefined) {\n routePaths.set(routeRef, newCollectedPath);\n newFoundRefForCollectedPath = true;\n }\n\n routeParents.set(routeRef, newParentRef);\n currentObj?.routeRefs.add(routeRef);\n if (current.spec.plugin) {\n currentObj?.plugins.add(toLegacyPlugin(current.spec.plugin));\n }\n }\n\n for (const children of current.edges.attachments.values()) {\n for (const child of children) {\n visit(\n child,\n newCollectedPath,\n newFoundRefForCollectedPath,\n newParentRef,\n newCandidateParentRef,\n currentObj,\n );\n }\n }\n }\n\n visit(node);\n\n return {\n routePaths,\n routeParents,\n routeObjects,\n routeAliasResolver: createExactRouteAliasResolver(routeAliases),\n };\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import mapValues from 'lodash/mapValues';
2
2
  import { toInternalExtension } from '../frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js';
3
3
  import { createExtensionDataContainer } from '../frontend-internal/src/wiring/createExtensionDataContainer.esm.js';
4
+ import '../frontend-internal/src/wiring/InternalSwappableComponentRef.esm.js';
4
5
  import '../frontend-internal/src/wiring/InternalExtensionDefinition.esm.js';
5
6
  import '../frontend-internal/src/wiring/InternalFrontendPlugin.esm.js';
6
7
 
@@ -178,10 +179,15 @@ function createAppNodeInstance(options) {
178
179
  apis: context.apis,
179
180
  inputs: context.inputs,
180
181
  config: overrideContext?.config ?? context.config
181
- })
182
+ }),
183
+ "extension factory"
182
184
  );
183
- }, context)
185
+ }, context),
186
+ "extension factory middleware"
184
187
  ) : internalExtension.factory(context);
188
+ if (typeof outputDataValues !== "object" || !outputDataValues?.[Symbol.iterator]) {
189
+ throw new Error("extension factory did not provide an iterable object");
190
+ }
185
191
  const outputDataMap = /* @__PURE__ */ new Map();
186
192
  for (const value of outputDataValues) {
187
193
  if (outputDataMap.has(value.id)) {
@@ -1 +1 @@
1
- {"version":3,"file":"instantiateAppNodeTree.esm.js","sources":["../../src/tree/instantiateAppNodeTree.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 AnyExtensionDataRef,\n ApiHolder,\n ExtensionDataContainer,\n ExtensionDataRef,\n ExtensionFactoryMiddleware,\n ExtensionInput,\n ResolvedExtensionInputs,\n} from '@backstage/frontend-plugin-api';\nimport mapValues from 'lodash/mapValues';\nimport { AppNode, AppNodeInstance } from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\nimport { createExtensionDataContainer } from '@internal/frontend';\n\ntype Mutable<T> = {\n -readonly [P in keyof T]: T[P];\n};\n\nfunction resolveV1InputDataMap(\n dataMap: {\n [name in string]: AnyExtensionDataRef;\n },\n attachment: AppNode,\n inputName: string,\n) {\n return mapValues(dataMap, ref => {\n const value = attachment.instance?.getData(ref);\n if (value === undefined && !ref.config.optional) {\n const expected = Object.values(dataMap)\n .filter(r => !r.config.optional)\n .map(r => `'${r.id}'`)\n .join(', ');\n\n const provided = [...(attachment.instance?.getDataRefs() ?? [])]\n .map(r => `'${r.id}'`)\n .join(', ');\n\n throw new Error(\n `extension '${attachment.spec.id}' could not be attached because its output data (${provided}) does not match what the input '${inputName}' requires (${expected})`,\n );\n }\n return value;\n });\n}\n\nfunction resolveInputDataContainer(\n extensionData: Array<AnyExtensionDataRef>,\n attachment: AppNode,\n inputName: string,\n): { node: AppNode } & ExtensionDataContainer<AnyExtensionDataRef> {\n const dataMap = new Map<string, unknown>();\n\n for (const ref of extensionData) {\n if (dataMap.has(ref.id)) {\n throw new Error(`Unexpected duplicate input data '${ref.id}'`);\n }\n const value = attachment.instance?.getData(ref);\n if (value === undefined && !ref.config.optional) {\n const expected = extensionData\n .filter(r => !r.config.optional)\n .map(r => `'${r.id}'`)\n .join(', ');\n\n const provided = [...(attachment.instance?.getDataRefs() ?? [])]\n .map(r => `'${r.id}'`)\n .join(', ');\n\n throw new Error(\n `extension '${attachment.spec.id}' could not be attached because its output data (${provided}) does not match what the input '${inputName}' requires (${expected})`,\n );\n }\n\n dataMap.set(ref.id, value);\n }\n\n return {\n node: attachment,\n get(ref) {\n return dataMap.get(ref.id);\n },\n *[Symbol.iterator]() {\n for (const [id, value] of dataMap) {\n // TODO: Would be better to be able to create a new instance using the ref here instead\n yield {\n $$type: '@backstage/ExtensionDataValue',\n id,\n value,\n };\n }\n },\n } as { node: AppNode } & ExtensionDataContainer<AnyExtensionDataRef>;\n}\n\nfunction reportUndeclaredAttachments(\n id: string,\n inputMap: { [name in string]: unknown },\n attachments: ReadonlyMap<string, AppNode[]>,\n) {\n const undeclaredAttachments = Array.from(attachments.entries()).filter(\n ([inputName]) => inputMap[inputName] === undefined,\n );\n\n const inputNames = Object.keys(inputMap);\n\n for (const [name, nodes] of undeclaredAttachments) {\n const pl = nodes.length > 1;\n // eslint-disable-next-line no-console\n console.warn(\n [\n `The extension${pl ? 's' : ''} '${nodes\n .map(n => n.spec.id)\n .join(\"', '\")}' ${pl ? 'are' : 'is'}`,\n `attached to the input '${name}' of the extension '${id}', but it`,\n inputNames.length === 0\n ? 'has no inputs'\n : `has no such input (candidates are '${inputNames.join(\"', '\")}')`,\n ].join(' '),\n );\n }\n}\n\nfunction resolveV1Inputs(\n inputMap: {\n [inputName in string]: {\n $$type: '@backstage/ExtensionInput';\n extensionData: {\n [name in string]: AnyExtensionDataRef;\n };\n config: { optional: boolean; singleton: boolean };\n };\n },\n attachments: ReadonlyMap<string, AppNode[]>,\n) {\n return mapValues(inputMap, (input, inputName) => {\n const attachedNodes = attachments.get(inputName) ?? [];\n\n if (input.config.singleton) {\n if (attachedNodes.length > 1) {\n const attachedNodeIds = attachedNodes.map(e => e.spec.id);\n throw Error(\n `expected ${\n input.config.optional ? 'at most' : 'exactly'\n } one '${inputName}' input but received multiple: '${attachedNodeIds.join(\n \"', '\",\n )}'`,\n );\n } else if (attachedNodes.length === 0) {\n if (input.config.optional) {\n return undefined;\n }\n throw Error(`input '${inputName}' is required but was not received`);\n }\n return {\n node: attachedNodes[0],\n output: resolveV1InputDataMap(\n input.extensionData,\n attachedNodes[0],\n inputName,\n ),\n };\n }\n\n return attachedNodes.map(attachment => ({\n node: attachment,\n output: resolveV1InputDataMap(input.extensionData, attachment, inputName),\n }));\n }) as {\n [inputName in string]: {\n node: AppNode;\n output: {\n [name in string]: unknown;\n };\n };\n };\n}\n\nfunction resolveV2Inputs(\n inputMap: {\n [inputName in string]: ExtensionInput<\n AnyExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n },\n attachments: ReadonlyMap<string, AppNode[]>,\n): ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput<\n AnyExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n}> {\n return mapValues(inputMap, (input, inputName) => {\n const attachedNodes = attachments.get(inputName) ?? [];\n\n if (input.config.singleton) {\n if (attachedNodes.length > 1) {\n const attachedNodeIds = attachedNodes.map(e => e.spec.id);\n throw Error(\n `expected ${\n input.config.optional ? 'at most' : 'exactly'\n } one '${inputName}' input but received multiple: '${attachedNodeIds.join(\n \"', '\",\n )}'`,\n );\n } else if (attachedNodes.length === 0) {\n if (input.config.optional) {\n return undefined;\n }\n throw Error(`input '${inputName}' is required but was not received`);\n }\n return resolveInputDataContainer(\n input.extensionData,\n attachedNodes[0],\n inputName,\n );\n }\n\n return attachedNodes.map(attachment =>\n resolveInputDataContainer(input.extensionData, attachment, inputName),\n );\n }) as ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput<\n AnyExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n }>;\n}\n\n/** @internal */\nexport function createAppNodeInstance(options: {\n extensionFactoryMiddleware?: ExtensionFactoryMiddleware;\n node: AppNode;\n apis: ApiHolder;\n attachments: ReadonlyMap<string, AppNode[]>;\n}): AppNodeInstance {\n const { node, apis, attachments } = options;\n const { id, extension, config } = node.spec;\n const extensionData = new Map<string, unknown>();\n const extensionDataRefs = new Set<ExtensionDataRef<unknown>>();\n\n let parsedConfig: { [x: string]: any };\n try {\n parsedConfig = extension.configSchema?.parse(config ?? {}) as {\n [x: string]: any;\n };\n } catch (e) {\n throw new Error(\n `Invalid configuration for extension '${id}'; caused by ${e}`,\n );\n }\n\n try {\n const internalExtension = toInternalExtension(extension);\n\n if (process.env.NODE_ENV !== 'production') {\n reportUndeclaredAttachments(id, internalExtension.inputs, attachments);\n }\n\n if (internalExtension.version === 'v1') {\n const namedOutputs = internalExtension.factory({\n node,\n apis,\n config: parsedConfig,\n inputs: resolveV1Inputs(internalExtension.inputs, attachments),\n });\n\n for (const [name, output] of Object.entries(namedOutputs)) {\n const ref = internalExtension.output[name];\n if (!ref) {\n throw new Error(`unknown output provided via '${name}'`);\n }\n if (extensionData.has(ref.id)) {\n throw new Error(\n `duplicate extension data '${ref.id}' received via output '${name}'`,\n );\n }\n extensionData.set(ref.id, output);\n extensionDataRefs.add(ref);\n }\n } else if (internalExtension.version === 'v2') {\n const context = {\n node,\n apis,\n config: parsedConfig,\n inputs: resolveV2Inputs(internalExtension.inputs, attachments),\n };\n const outputDataValues = options.extensionFactoryMiddleware\n ? createExtensionDataContainer(\n options.extensionFactoryMiddleware(overrideContext => {\n return createExtensionDataContainer(\n internalExtension.factory({\n node: context.node,\n apis: context.apis,\n inputs: context.inputs,\n config: overrideContext?.config ?? context.config,\n }),\n );\n }, context),\n )\n : internalExtension.factory(context);\n\n const outputDataMap = new Map<string, unknown>();\n for (const value of outputDataValues) {\n if (outputDataMap.has(value.id)) {\n throw new Error(`duplicate extension data output '${value.id}'`);\n }\n outputDataMap.set(value.id, value.value);\n }\n\n for (const ref of internalExtension.output) {\n const value = outputDataMap.get(ref.id);\n outputDataMap.delete(ref.id);\n if (value === undefined) {\n if (!ref.config.optional) {\n throw new Error(\n `missing required extension data output '${ref.id}'`,\n );\n }\n } else {\n extensionData.set(ref.id, value);\n extensionDataRefs.add(ref);\n }\n }\n\n if (outputDataMap.size > 0) {\n throw new Error(\n `unexpected output '${Array.from(outputDataMap.keys()).join(\n \"', '\",\n )}'`,\n );\n }\n } else {\n throw new Error(\n `unexpected extension version '${(internalExtension as any).version}'`,\n );\n }\n } catch (e) {\n throw new Error(\n `Failed to instantiate extension '${id}'${\n e.name === 'Error' ? `, ${e.message}` : `; caused by ${e.stack}`\n }`,\n );\n }\n\n return {\n getDataRefs() {\n return extensionDataRefs.values();\n },\n getData<T>(ref: ExtensionDataRef<T>): T | undefined {\n return extensionData.get(ref.id) as T | undefined;\n },\n };\n}\n\n/**\n * Starting at the provided node, instantiate all reachable nodes in the tree that have not been disabled.\n * @internal\n */\nexport function instantiateAppNodeTree(\n rootNode: AppNode,\n apis: ApiHolder,\n extensionFactoryMiddleware?: ExtensionFactoryMiddleware,\n): void {\n function createInstance(node: AppNode): AppNodeInstance | undefined {\n if (node.instance) {\n return node.instance;\n }\n if (node.spec.disabled) {\n return undefined;\n }\n\n const instantiatedAttachments = new Map<string, AppNode[]>();\n\n for (const [input, children] of node.edges.attachments) {\n const instantiatedChildren = children.flatMap(child => {\n const childInstance = createInstance(child);\n if (!childInstance) {\n return [];\n }\n return [child];\n });\n if (instantiatedChildren.length > 0) {\n instantiatedAttachments.set(input, instantiatedChildren);\n }\n }\n\n (node as Mutable<AppNode>).instance = createAppNodeInstance({\n extensionFactoryMiddleware,\n node,\n apis,\n attachments: instantiatedAttachments,\n });\n\n return node.instance;\n }\n\n createInstance(rootNode);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"instantiateAppNodeTree.esm.js","sources":["../../src/tree/instantiateAppNodeTree.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 ExtensionDataContainer,\n ExtensionDataRef,\n ExtensionFactoryMiddleware,\n ExtensionInput,\n ResolvedExtensionInputs,\n} from '@backstage/frontend-plugin-api';\nimport mapValues from 'lodash/mapValues';\nimport { AppNode, AppNodeInstance } from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\nimport { createExtensionDataContainer } from '@internal/frontend';\n\ntype Mutable<T> = {\n -readonly [P in keyof T]: T[P];\n};\n\nfunction resolveV1InputDataMap(\n dataMap: {\n [name in string]: ExtensionDataRef;\n },\n attachment: AppNode,\n inputName: string,\n) {\n return mapValues(dataMap, ref => {\n const value = attachment.instance?.getData(ref);\n if (value === undefined && !ref.config.optional) {\n const expected = Object.values(dataMap)\n .filter(r => !r.config.optional)\n .map(r => `'${r.id}'`)\n .join(', ');\n\n const provided = [...(attachment.instance?.getDataRefs() ?? [])]\n .map(r => `'${r.id}'`)\n .join(', ');\n\n throw new Error(\n `extension '${attachment.spec.id}' could not be attached because its output data (${provided}) does not match what the input '${inputName}' requires (${expected})`,\n );\n }\n return value;\n });\n}\n\nfunction resolveInputDataContainer(\n extensionData: Array<ExtensionDataRef>,\n attachment: AppNode,\n inputName: string,\n): { node: AppNode } & ExtensionDataContainer<ExtensionDataRef> {\n const dataMap = new Map<string, unknown>();\n\n for (const ref of extensionData) {\n if (dataMap.has(ref.id)) {\n throw new Error(`Unexpected duplicate input data '${ref.id}'`);\n }\n const value = attachment.instance?.getData(ref);\n if (value === undefined && !ref.config.optional) {\n const expected = extensionData\n .filter(r => !r.config.optional)\n .map(r => `'${r.id}'`)\n .join(', ');\n\n const provided = [...(attachment.instance?.getDataRefs() ?? [])]\n .map(r => `'${r.id}'`)\n .join(', ');\n\n throw new Error(\n `extension '${attachment.spec.id}' could not be attached because its output data (${provided}) does not match what the input '${inputName}' requires (${expected})`,\n );\n }\n\n dataMap.set(ref.id, value);\n }\n\n return {\n node: attachment,\n get(ref) {\n return dataMap.get(ref.id);\n },\n *[Symbol.iterator]() {\n for (const [id, value] of dataMap) {\n // TODO: Would be better to be able to create a new instance using the ref here instead\n yield {\n $$type: '@backstage/ExtensionDataValue',\n id,\n value,\n };\n }\n },\n } as { node: AppNode } & ExtensionDataContainer<ExtensionDataRef>;\n}\n\nfunction reportUndeclaredAttachments(\n id: string,\n inputMap: { [name in string]: unknown },\n attachments: ReadonlyMap<string, AppNode[]>,\n) {\n const undeclaredAttachments = Array.from(attachments.entries()).filter(\n ([inputName]) => inputMap[inputName] === undefined,\n );\n\n const inputNames = Object.keys(inputMap);\n\n for (const [name, nodes] of undeclaredAttachments) {\n const pl = nodes.length > 1;\n // eslint-disable-next-line no-console\n console.warn(\n [\n `The extension${pl ? 's' : ''} '${nodes\n .map(n => n.spec.id)\n .join(\"', '\")}' ${pl ? 'are' : 'is'}`,\n `attached to the input '${name}' of the extension '${id}', but it`,\n inputNames.length === 0\n ? 'has no inputs'\n : `has no such input (candidates are '${inputNames.join(\"', '\")}')`,\n ].join(' '),\n );\n }\n}\n\nfunction resolveV1Inputs(\n inputMap: {\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 attachments: ReadonlyMap<string, AppNode[]>,\n) {\n return mapValues(inputMap, (input, inputName) => {\n const attachedNodes = attachments.get(inputName) ?? [];\n\n if (input.config.singleton) {\n if (attachedNodes.length > 1) {\n const attachedNodeIds = attachedNodes.map(e => e.spec.id);\n throw Error(\n `expected ${\n input.config.optional ? 'at most' : 'exactly'\n } one '${inputName}' input but received multiple: '${attachedNodeIds.join(\n \"', '\",\n )}'`,\n );\n } else if (attachedNodes.length === 0) {\n if (input.config.optional) {\n return undefined;\n }\n throw Error(`input '${inputName}' is required but was not received`);\n }\n return {\n node: attachedNodes[0],\n output: resolveV1InputDataMap(\n input.extensionData,\n attachedNodes[0],\n inputName,\n ),\n };\n }\n\n return attachedNodes.map(attachment => ({\n node: attachment,\n output: resolveV1InputDataMap(input.extensionData, attachment, inputName),\n }));\n }) as {\n [inputName in string]: {\n node: AppNode;\n output: {\n [name in string]: unknown;\n };\n };\n };\n}\n\nfunction resolveV2Inputs(\n inputMap: {\n [inputName in string]: ExtensionInput<\n ExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n },\n attachments: ReadonlyMap<string, AppNode[]>,\n): ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput<\n ExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n}> {\n return mapValues(inputMap, (input, inputName) => {\n const attachedNodes = attachments.get(inputName) ?? [];\n\n if (input.config.singleton) {\n if (attachedNodes.length > 1) {\n const attachedNodeIds = attachedNodes.map(e => e.spec.id);\n throw Error(\n `expected ${\n input.config.optional ? 'at most' : 'exactly'\n } one '${inputName}' input but received multiple: '${attachedNodeIds.join(\n \"', '\",\n )}'`,\n );\n } else if (attachedNodes.length === 0) {\n if (input.config.optional) {\n return undefined;\n }\n throw Error(`input '${inputName}' is required but was not received`);\n }\n return resolveInputDataContainer(\n input.extensionData,\n attachedNodes[0],\n inputName,\n );\n }\n\n return attachedNodes.map(attachment =>\n resolveInputDataContainer(input.extensionData, attachment, inputName),\n );\n }) as ResolvedExtensionInputs<{\n [inputName in string]: ExtensionInput<\n ExtensionDataRef,\n { optional: boolean; singleton: boolean }\n >;\n }>;\n}\n\n/** @internal */\nexport function createAppNodeInstance(options: {\n extensionFactoryMiddleware?: ExtensionFactoryMiddleware;\n node: AppNode;\n apis: ApiHolder;\n attachments: ReadonlyMap<string, AppNode[]>;\n}): AppNodeInstance {\n const { node, apis, attachments } = options;\n const { id, extension, config } = node.spec;\n const extensionData = new Map<string, unknown>();\n const extensionDataRefs = new Set<ExtensionDataRef<unknown>>();\n\n let parsedConfig: { [x: string]: any };\n try {\n parsedConfig = extension.configSchema?.parse(config ?? {}) as {\n [x: string]: any;\n };\n } catch (e) {\n throw new Error(\n `Invalid configuration for extension '${id}'; caused by ${e}`,\n );\n }\n\n try {\n const internalExtension = toInternalExtension(extension);\n\n if (process.env.NODE_ENV !== 'production') {\n reportUndeclaredAttachments(id, internalExtension.inputs, attachments);\n }\n\n if (internalExtension.version === 'v1') {\n const namedOutputs = internalExtension.factory({\n node,\n apis,\n config: parsedConfig,\n inputs: resolveV1Inputs(internalExtension.inputs, attachments),\n });\n\n for (const [name, output] of Object.entries(namedOutputs)) {\n const ref = internalExtension.output[name];\n if (!ref) {\n throw new Error(`unknown output provided via '${name}'`);\n }\n if (extensionData.has(ref.id)) {\n throw new Error(\n `duplicate extension data '${ref.id}' received via output '${name}'`,\n );\n }\n extensionData.set(ref.id, output);\n extensionDataRefs.add(ref);\n }\n } else if (internalExtension.version === 'v2') {\n const context = {\n node,\n apis,\n config: parsedConfig,\n inputs: resolveV2Inputs(internalExtension.inputs, attachments),\n };\n const outputDataValues = options.extensionFactoryMiddleware\n ? createExtensionDataContainer(\n options.extensionFactoryMiddleware(overrideContext => {\n return createExtensionDataContainer(\n internalExtension.factory({\n node: context.node,\n apis: context.apis,\n inputs: context.inputs,\n config: overrideContext?.config ?? context.config,\n }),\n 'extension factory',\n );\n }, context),\n 'extension factory middleware',\n )\n : internalExtension.factory(context);\n\n if (\n typeof outputDataValues !== 'object' ||\n !outputDataValues?.[Symbol.iterator]\n ) {\n throw new Error('extension factory did not provide an iterable object');\n }\n\n const outputDataMap = new Map<string, unknown>();\n for (const value of outputDataValues) {\n if (outputDataMap.has(value.id)) {\n throw new Error(`duplicate extension data output '${value.id}'`);\n }\n outputDataMap.set(value.id, value.value);\n }\n\n for (const ref of internalExtension.output) {\n const value = outputDataMap.get(ref.id);\n outputDataMap.delete(ref.id);\n if (value === undefined) {\n if (!ref.config.optional) {\n throw new Error(\n `missing required extension data output '${ref.id}'`,\n );\n }\n } else {\n extensionData.set(ref.id, value);\n extensionDataRefs.add(ref);\n }\n }\n\n if (outputDataMap.size > 0) {\n throw new Error(\n `unexpected output '${Array.from(outputDataMap.keys()).join(\n \"', '\",\n )}'`,\n );\n }\n } else {\n throw new Error(\n `unexpected extension version '${(internalExtension as any).version}'`,\n );\n }\n } catch (e) {\n throw new Error(\n `Failed to instantiate extension '${id}'${\n e.name === 'Error' ? `, ${e.message}` : `; caused by ${e.stack}`\n }`,\n );\n }\n\n return {\n getDataRefs() {\n return extensionDataRefs.values();\n },\n getData<T>(ref: ExtensionDataRef<T>): T | undefined {\n return extensionData.get(ref.id) as T | undefined;\n },\n };\n}\n\n/**\n * Starting at the provided node, instantiate all reachable nodes in the tree that have not been disabled.\n * @internal\n */\nexport function instantiateAppNodeTree(\n rootNode: AppNode,\n apis: ApiHolder,\n extensionFactoryMiddleware?: ExtensionFactoryMiddleware,\n): void {\n function createInstance(node: AppNode): AppNodeInstance | undefined {\n if (node.instance) {\n return node.instance;\n }\n if (node.spec.disabled) {\n return undefined;\n }\n\n const instantiatedAttachments = new Map<string, AppNode[]>();\n\n for (const [input, children] of node.edges.attachments) {\n const instantiatedChildren = children.flatMap(child => {\n const childInstance = createInstance(child);\n if (!childInstance) {\n return [];\n }\n return [child];\n });\n if (instantiatedChildren.length > 0) {\n instantiatedAttachments.set(input, instantiatedChildren);\n }\n }\n\n (node as Mutable<AppNode>).instance = createAppNodeInstance({\n extensionFactoryMiddleware,\n node,\n apis,\n attachments: instantiatedAttachments,\n });\n\n return node.instance;\n }\n\n createInstance(rootNode);\n}\n"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
1
2
  import { isInternalFrontendModule, toInternalFrontendModule } from '../frontend-plugin-api/src/wiring/createFrontendModule.esm.js';
2
3
  import { toInternalExtension } from '../frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js';
3
4
  import { OpaqueFrontendPlugin } from '../frontend-internal/src/wiring/InternalFrontendPlugin.esm.js';
@@ -43,6 +44,9 @@ function resolveAppNodeSpecs(options) {
43
44
  `It is forbidden to override the following extension(s): ${forbiddenStr}, which is done by a module for the following plugin(s): ${pluginsStr}`
44
45
  );
45
46
  }
47
+ const appPlugin = plugins.find((plugin) => plugin.id === "app") ?? createFrontendPlugin({
48
+ pluginId: "app"
49
+ });
46
50
  const configuredExtensions = [
47
51
  ...pluginExtensions.map(({ plugin, ...extension }) => {
48
52
  const internalExtension = toInternalExtension(extension);
@@ -62,8 +66,8 @@ function resolveAppNodeSpecs(options) {
62
66
  return {
63
67
  extension: internalExtension,
64
68
  params: {
65
- source: void 0,
66
- plugin: void 0,
69
+ source: appPlugin,
70
+ plugin: appPlugin,
67
71
  attachTo: internalExtension.attachTo,
68
72
  disabled: internalExtension.disabled,
69
73
  config: void 0
@@ -1 +1 @@
1
- {"version":3,"file":"resolveAppNodeSpecs.esm.js","sources":["../../src/tree/resolveAppNodeSpecs.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 { Extension } from '@backstage/frontend-plugin-api';\nimport { ExtensionParameters } from './readAppExtensionsConfig';\nimport { AppNodeSpec } from '@backstage/frontend-plugin-api';\nimport { OpaqueFrontendPlugin } from '@internal/frontend';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isInternalFrontendModule,\n toInternalFrontendModule,\n} from '../../../frontend-plugin-api/src/wiring/createFrontendModule';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\nimport { FrontendFeature } from '../wiring/types';\n\n/** @internal */\nexport function resolveAppNodeSpecs(options: {\n features?: FrontendFeature[];\n builtinExtensions?: Extension<any, any>[];\n parameters?: Array<ExtensionParameters>;\n forbidden?: Set<string>;\n allowUnknownExtensionConfig?: boolean;\n}): AppNodeSpec[] {\n const {\n builtinExtensions = [],\n parameters = [],\n forbidden = new Set(),\n features = [],\n allowUnknownExtensionConfig = false,\n } = options;\n\n const plugins = features.filter(OpaqueFrontendPlugin.isType);\n const modules = features.filter(isInternalFrontendModule);\n\n const pluginExtensions = plugins.flatMap(plugin => {\n return OpaqueFrontendPlugin.toInternal(plugin).extensions.map(\n extension => ({\n ...extension,\n plugin,\n }),\n );\n });\n const moduleExtensions = modules.flatMap(mod =>\n toInternalFrontendModule(mod).extensions.flatMap(extension => {\n // Modules for plugins that are not installed are ignored\n const plugin = plugins.find(p => p.id === mod.pluginId);\n if (!plugin) {\n return [];\n }\n\n return [{ ...extension, plugin }];\n }),\n );\n\n // Prevent core override\n if (pluginExtensions.some(({ id }) => forbidden.has(id))) {\n const pluginsStr = pluginExtensions\n .filter(({ id }) => forbidden.has(id))\n .map(({ plugin }) => `'${plugin.id}'`)\n .join(', ');\n const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', ');\n throw new Error(\n `It is forbidden to override the following extension(s): ${forbiddenStr}, which is done by the following plugin(s): ${pluginsStr}`,\n );\n }\n if (moduleExtensions.some(({ id }) => forbidden.has(id))) {\n const pluginsStr = moduleExtensions\n .filter(({ id }) => forbidden.has(id))\n .map(({ plugin }) => `'${plugin.id}'`)\n .join(', ');\n const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', ');\n throw new Error(\n `It is forbidden to override the following extension(s): ${forbiddenStr}, which is done by a module for the following plugin(s): ${pluginsStr}`,\n );\n }\n\n const configuredExtensions = [\n ...pluginExtensions.map(({ plugin, ...extension }) => {\n const internalExtension = toInternalExtension(extension);\n return {\n extension: internalExtension,\n params: {\n plugin,\n source: plugin,\n attachTo: internalExtension.attachTo,\n disabled: internalExtension.disabled,\n config: undefined as unknown,\n },\n };\n }),\n ...builtinExtensions.map(extension => {\n const internalExtension = toInternalExtension(extension);\n return {\n extension: internalExtension,\n params: {\n source: undefined,\n plugin: undefined,\n attachTo: internalExtension.attachTo,\n disabled: internalExtension.disabled,\n config: undefined as unknown,\n },\n };\n }),\n ];\n\n // Install all module overrides\n for (const extension of moduleExtensions) {\n const internalExtension = toInternalExtension(extension);\n\n // Check if our override is overriding an extension that already exists\n const index = configuredExtensions.findIndex(\n e => e.extension.id === extension.id,\n );\n if (index !== -1) {\n // Only implementation, attachment point and default disabled status are overridden, the source is kept\n configuredExtensions[index].extension = internalExtension;\n configuredExtensions[index].params.attachTo = internalExtension.attachTo;\n configuredExtensions[index].params.disabled = internalExtension.disabled;\n } else {\n // Add the extension as a new one when not overriding an existing one\n configuredExtensions.push({\n extension: internalExtension,\n params: {\n plugin: extension.plugin,\n source: extension.plugin,\n attachTo: internalExtension.attachTo,\n disabled: internalExtension.disabled,\n config: undefined,\n },\n });\n }\n }\n\n const duplicatedExtensionIds = new Set<string>();\n const duplicatedExtensionData = configuredExtensions.reduce<\n Record<string, Record<string, number>>\n >((data, { extension, params }) => {\n const extensionId = extension.id;\n const extensionData = data?.[extensionId];\n if (extensionData) duplicatedExtensionIds.add(extensionId);\n const pluginId = params.source?.id ?? 'internal';\n const pluginCount = extensionData?.[pluginId] ?? 0;\n return {\n ...data,\n [extensionId]: { ...extensionData, [pluginId]: pluginCount + 1 },\n };\n }, {});\n\n if (duplicatedExtensionIds.size > 0) {\n throw new Error(\n `The following extensions are duplicated: ${Array.from(\n duplicatedExtensionIds,\n )\n .map(\n extensionId =>\n `The extension '${extensionId}' was provided ${Object.keys(\n duplicatedExtensionData[extensionId],\n )\n .map(\n pluginId =>\n `${duplicatedExtensionData[extensionId][pluginId]} time(s) by the plugin '${pluginId}'`,\n )\n .join(' and ')}`,\n )\n .join(', ')}`,\n );\n }\n\n const order = new Map<string, (typeof configuredExtensions)[number]>();\n for (const overrideParam of parameters) {\n const extensionId = overrideParam.id;\n\n if (forbidden.has(extensionId)) {\n throw new Error(\n `Configuration of the '${extensionId}' extension is forbidden`,\n );\n }\n\n const existing = configuredExtensions.find(\n e => e.extension.id === extensionId,\n );\n if (existing) {\n if (overrideParam.attachTo) {\n existing.params.attachTo = overrideParam.attachTo;\n }\n if (overrideParam.config) {\n // TODO: merge config?\n existing.params.config = overrideParam.config;\n }\n if (\n Boolean(existing.params.disabled) !== Boolean(overrideParam.disabled)\n ) {\n existing.params.disabled = Boolean(overrideParam.disabled);\n }\n order.set(extensionId, existing);\n } else if (!allowUnknownExtensionConfig) {\n throw new Error(`Extension ${extensionId} does not exist`);\n }\n }\n\n const orderedExtensions = [\n ...order.values(),\n ...configuredExtensions.filter(e => !order.has(e.extension.id)),\n ];\n\n return orderedExtensions.map(param => ({\n id: param.extension.id,\n attachTo: param.params.attachTo,\n extension: param.extension,\n disabled: param.params.disabled,\n plugin: param.params.plugin,\n source: param.params.source,\n config: param.params.config,\n }));\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolveAppNodeSpecs.esm.js","sources":["../../src/tree/resolveAppNodeSpecs.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 createFrontendPlugin,\n Extension,\n FrontendFeature,\n} from '@backstage/frontend-plugin-api';\nimport { ExtensionParameters } from './readAppExtensionsConfig';\nimport { AppNodeSpec } from '@backstage/frontend-plugin-api';\nimport { OpaqueFrontendPlugin } from '@internal/frontend';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n isInternalFrontendModule,\n toInternalFrontendModule,\n} from '../../../frontend-plugin-api/src/wiring/createFrontendModule';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\n\n/** @internal */\nexport function resolveAppNodeSpecs(options: {\n features?: FrontendFeature[];\n builtinExtensions?: Extension<any, any>[];\n parameters?: Array<ExtensionParameters>;\n forbidden?: Set<string>;\n allowUnknownExtensionConfig?: boolean;\n}): AppNodeSpec[] {\n const {\n builtinExtensions = [],\n parameters = [],\n forbidden = new Set(),\n features = [],\n allowUnknownExtensionConfig = false,\n } = options;\n\n const plugins = features.filter(OpaqueFrontendPlugin.isType);\n const modules = features.filter(isInternalFrontendModule);\n\n const pluginExtensions = plugins.flatMap(plugin => {\n return OpaqueFrontendPlugin.toInternal(plugin).extensions.map(\n extension => ({\n ...extension,\n plugin,\n }),\n );\n });\n const moduleExtensions = modules.flatMap(mod =>\n toInternalFrontendModule(mod).extensions.flatMap(extension => {\n // Modules for plugins that are not installed are ignored\n const plugin = plugins.find(p => p.id === mod.pluginId);\n if (!plugin) {\n return [];\n }\n\n return [{ ...extension, plugin }];\n }),\n );\n\n // Prevent core override\n if (pluginExtensions.some(({ id }) => forbidden.has(id))) {\n const pluginsStr = pluginExtensions\n .filter(({ id }) => forbidden.has(id))\n .map(({ plugin }) => `'${plugin.id}'`)\n .join(', ');\n const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', ');\n throw new Error(\n `It is forbidden to override the following extension(s): ${forbiddenStr}, which is done by the following plugin(s): ${pluginsStr}`,\n );\n }\n if (moduleExtensions.some(({ id }) => forbidden.has(id))) {\n const pluginsStr = moduleExtensions\n .filter(({ id }) => forbidden.has(id))\n .map(({ plugin }) => `'${plugin.id}'`)\n .join(', ');\n const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', ');\n throw new Error(\n `It is forbidden to override the following extension(s): ${forbiddenStr}, which is done by a module for the following plugin(s): ${pluginsStr}`,\n );\n }\n\n const appPlugin =\n plugins.find(plugin => plugin.id === 'app') ??\n createFrontendPlugin({\n pluginId: 'app',\n });\n\n const configuredExtensions = [\n ...pluginExtensions.map(({ plugin, ...extension }) => {\n const internalExtension = toInternalExtension(extension);\n return {\n extension: internalExtension,\n params: {\n plugin,\n source: plugin,\n attachTo: internalExtension.attachTo,\n disabled: internalExtension.disabled,\n config: undefined as unknown,\n },\n };\n }),\n ...builtinExtensions.map(extension => {\n const internalExtension = toInternalExtension(extension);\n return {\n extension: internalExtension,\n params: {\n source: appPlugin,\n plugin: appPlugin,\n attachTo: internalExtension.attachTo,\n disabled: internalExtension.disabled,\n config: undefined as unknown,\n },\n };\n }),\n ];\n\n // Install all module overrides\n for (const extension of moduleExtensions) {\n const internalExtension = toInternalExtension(extension);\n\n // Check if our override is overriding an extension that already exists\n const index = configuredExtensions.findIndex(\n e => e.extension.id === extension.id,\n );\n if (index !== -1) {\n // Only implementation, attachment point and default disabled status are overridden, the source is kept\n configuredExtensions[index].extension = internalExtension;\n configuredExtensions[index].params.attachTo = internalExtension.attachTo;\n configuredExtensions[index].params.disabled = internalExtension.disabled;\n } else {\n // Add the extension as a new one when not overriding an existing one\n configuredExtensions.push({\n extension: internalExtension,\n params: {\n plugin: extension.plugin,\n source: extension.plugin,\n attachTo: internalExtension.attachTo,\n disabled: internalExtension.disabled,\n config: undefined,\n },\n });\n }\n }\n\n const duplicatedExtensionIds = new Set<string>();\n const duplicatedExtensionData = configuredExtensions.reduce<\n Record<string, Record<string, number>>\n >((data, { extension, params }) => {\n const extensionId = extension.id;\n const extensionData = data?.[extensionId];\n if (extensionData) duplicatedExtensionIds.add(extensionId);\n const pluginId = params.source?.id ?? 'internal';\n const pluginCount = extensionData?.[pluginId] ?? 0;\n return {\n ...data,\n [extensionId]: { ...extensionData, [pluginId]: pluginCount + 1 },\n };\n }, {});\n\n if (duplicatedExtensionIds.size > 0) {\n throw new Error(\n `The following extensions are duplicated: ${Array.from(\n duplicatedExtensionIds,\n )\n .map(\n extensionId =>\n `The extension '${extensionId}' was provided ${Object.keys(\n duplicatedExtensionData[extensionId],\n )\n .map(\n pluginId =>\n `${duplicatedExtensionData[extensionId][pluginId]} time(s) by the plugin '${pluginId}'`,\n )\n .join(' and ')}`,\n )\n .join(', ')}`,\n );\n }\n\n const order = new Map<string, (typeof configuredExtensions)[number]>();\n for (const overrideParam of parameters) {\n const extensionId = overrideParam.id;\n\n if (forbidden.has(extensionId)) {\n throw new Error(\n `Configuration of the '${extensionId}' extension is forbidden`,\n );\n }\n\n const existing = configuredExtensions.find(\n e => e.extension.id === extensionId,\n );\n if (existing) {\n if (overrideParam.attachTo) {\n existing.params.attachTo = overrideParam.attachTo;\n }\n if (overrideParam.config) {\n // TODO: merge config?\n existing.params.config = overrideParam.config;\n }\n if (\n Boolean(existing.params.disabled) !== Boolean(overrideParam.disabled)\n ) {\n existing.params.disabled = Boolean(overrideParam.disabled);\n }\n order.set(extensionId, existing);\n } else if (!allowUnknownExtensionConfig) {\n throw new Error(`Extension ${extensionId} does not exist`);\n }\n }\n\n const orderedExtensions = [\n ...order.values(),\n ...configuredExtensions.filter(e => !order.has(e.extension.id)),\n ];\n\n return orderedExtensions.map(param => ({\n id: param.extension.id,\n attachTo: param.params.attachTo,\n extension: param.extension,\n disabled: param.params.disabled,\n plugin: param.params.plugin,\n source: param.params.source,\n config: param.params.config,\n }));\n}\n"],"names":[],"mappings":"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"}
@@ -18,6 +18,7 @@ import { ApiRegistry } from '../core-app-api/src/apis/system/ApiRegistry.esm.js'
18
18
  import { AppIdentityProxy } from '../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.esm.js';
19
19
  import { matchRoutes } from 'react-router-dom';
20
20
  import { createPluginInfoAttacher } from './createPluginInfoAttacher.esm.js';
21
+ import { createRouteAliasResolver } from '../routing/RouteAliasResolver.esm.js';
21
22
  import { OpaqueFrontendPlugin } from '../frontend-internal/src/wiring/InternalFrontendPlugin.esm.js';
22
23
  import { createExtensionDataContainer } from '../frontend-internal/src/wiring/createExtensionDataContainer.esm.js';
23
24
 
@@ -52,9 +53,9 @@ class AppTreeApiProxy {
52
53
  this.checkIfInitialized();
53
54
  return { tree: this.tree };
54
55
  }
55
- getNodesByRoutePath(sourcePath) {
56
+ getNodesByRoutePath(routePath) {
56
57
  this.checkIfInitialized();
57
- let path = sourcePath;
58
+ let path = routePath;
58
59
  if (path.startsWith(this.appBasePath)) {
59
60
  path = path.slice(this.appBasePath.length);
60
61
  }
@@ -87,7 +88,8 @@ class RouteResolutionApiProxy {
87
88
  routeInfo.routeParents,
88
89
  routeInfo.routeObjects,
89
90
  this.routeBindings,
90
- this.appBasePath
91
+ this.appBasePath,
92
+ routeInfo.routeAliasResolver
91
93
  );
92
94
  this.#routeObjects = routeInfo.routeObjects;
93
95
  return routeInfo;
@@ -99,7 +101,7 @@ class RouteResolutionApiProxy {
99
101
  function createSpecializedApp(options) {
100
102
  const config = options?.config ?? new ConfigReader({}, "empty-config");
101
103
  const features = deduplicateFeatures(options?.features ?? []).map(
102
- createPluginInfoAttacher(config, options?.pluginInfoResolver)
104
+ createPluginInfoAttacher(config, options?.advanced?.pluginInfoResolver)
103
105
  );
104
106
  const tree = resolveAppTree(
105
107
  "root",
@@ -110,22 +112,19 @@ function createSpecializedApp(options) {
110
112
  ],
111
113
  parameters: readAppExtensionsConfig(config),
112
114
  forbidden: /* @__PURE__ */ new Set(["root"]),
113
- allowUnknownExtensionConfig: options?.flags?.allowUnknownExtensionConfig
115
+ allowUnknownExtensionConfig: options?.advanced?.allowUnknownExtensionConfig
114
116
  })
115
117
  );
116
118
  const factories = createApiFactories({ tree });
117
119
  const appBasePath = getBasePath(config);
118
120
  const appTreeApi = new AppTreeApiProxy(tree, appBasePath);
121
+ const routeRefsById = collectRouteIds(features);
119
122
  const routeResolutionApi = new RouteResolutionApiProxy(
120
- resolveRouteBindings(
121
- options?.bindRoutes,
122
- config,
123
- collectRouteIds(features)
124
- ),
123
+ resolveRouteBindings(options?.bindRoutes, config, routeRefsById),
125
124
  appBasePath
126
125
  );
127
126
  const appIdentityProxy = new AppIdentityProxy();
128
- const apis = options?.apis ?? createApiHolder({
127
+ const apis = options?.advanced?.apis ?? createApiHolder({
129
128
  factories,
130
129
  staticFactories: [
131
130
  createApiFactory(appTreeApiRef, appTreeApi),
@@ -158,9 +157,14 @@ function createSpecializedApp(options) {
158
157
  instantiateAppNodeTree(
159
158
  tree.root,
160
159
  apis,
161
- mergeExtensionFactoryMiddleware(options?.extensionFactoryMiddleware)
160
+ mergeExtensionFactoryMiddleware(
161
+ options?.advanced?.extensionFactoryMiddleware
162
+ )
163
+ );
164
+ const routeInfo = extractRouteInfoFromAppNode(
165
+ tree.root,
166
+ createRouteAliasResolver(routeRefsById)
162
167
  );
163
- const routeInfo = extractRouteInfoFromAppNode(tree.root);
164
168
  routeResolutionApi.initialize(routeInfo);
165
169
  appTreeApi.initialize(routeInfo);
166
170
  return { apis, tree };
@@ -216,7 +220,8 @@ function mergeExtensionFactoryMiddleware(middlewares) {
216
220
  node: ctx.node,
217
221
  apis: ctx.apis,
218
222
  config: ctxOverrides?.config ?? ctx.config
219
- })
223
+ }),
224
+ "extension factory middleware"
220
225
  );
221
226
  }, ctx);
222
227
  };
@@ -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 { ConfigReader } from '@backstage/config';\nimport {\n ApiBlueprint,\n AppTree,\n AppTreeApi,\n appTreeApiRef,\n RouteRef,\n ExternalRouteRef,\n SubRouteRef,\n AnyRouteRefParams,\n RouteFunc,\n RouteResolutionApiResolveOptions,\n RouteResolutionApi,\n createApiFactory,\n routeResolutionApiRef,\n AppNode,\n ExtensionFactoryMiddleware,\n FrontendFeature,\n} from '@backstage/frontend-plugin-api';\nimport {\n AnyApiFactory,\n ApiHolder,\n ConfigApi,\n configApiRef,\n featureFlagsApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\nimport { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api';\nimport {\n createExtensionDataContainer,\n OpaqueFrontendPlugin,\n} from '@internal/frontend';\n\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n resolveExtensionDefinition,\n toInternalExtension,\n} from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\n\nimport { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode';\n\nimport { CreateAppRouteBinder } from '../routing';\nimport { RouteResolver } from '../routing/RouteResolver';\nimport { resolveRouteBindings } from '../routing/resolveRouteBindings';\nimport { collectRouteIds } from '../routing/collectRouteIds';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n toInternalFrontendModule,\n isInternalFrontendModule,\n} from '../../../frontend-plugin-api/src/wiring/createFrontendModule';\nimport { getBasePath } from '../routing/getBasePath';\nimport { Root } from '../extensions/Root';\nimport { resolveAppTree } from '../tree/resolveAppTree';\nimport { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs';\nimport { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig';\nimport { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';\nimport { BackstageRouteObject } from '../routing/types';\nimport { RouteInfo } from './types';\nimport { matchRoutes } from 'react-router-dom';\nimport {\n createPluginInfoAttacher,\n FrontendPluginInfoResolver,\n} from './createPluginInfoAttacher';\n\nfunction deduplicateFeatures(\n allFeatures: FrontendFeature[],\n): FrontendFeature[] {\n // Start by removing duplicates by reference\n const features = Array.from(new Set(allFeatures));\n\n // Plugins are deduplicated by ID, last one wins\n const seenIds = new Set<string>();\n return features\n .reverse()\n .filter(feature => {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n return true;\n }\n if (seenIds.has(feature.id)) {\n return false;\n }\n seenIds.add(feature.id);\n return true;\n })\n .reverse();\n}\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nclass AppTreeApiProxy implements AppTreeApi {\n #routeInfo?: RouteInfo;\n\n constructor(\n private readonly tree: AppTree,\n private readonly appBasePath: string,\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(sourcePath: string): { nodes: AppNode[] } {\n this.checkIfInitialized();\n\n let path = sourcePath;\n if (path.startsWith(this.appBasePath)) {\n path = path.slice(this.appBasePath.length);\n }\n\n const matchedRoutes = matchRoutes(this.#routeInfo!.routeObjects, path);\n\n const matchedAppNodes =\n matchedRoutes\n ?.filter(routeObj => !!routeObj.route.appNode)\n .map(routeObj => routeObj.route.appNode!) || [];\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\nclass RouteResolutionApiProxy implements RouteResolutionApi {\n #delegate: RouteResolutionApi | undefined;\n #routeObjects: BackstageRouteObject[] | undefined;\n\n constructor(\n private readonly routeBindings: Map<\n ExternalRouteRef,\n RouteRef | SubRouteRef\n >,\n private readonly appBasePath: string,\n ) {}\n\n resolve<TParams extends AnyRouteRefParams>(\n anyRouteRef:\n | RouteRef<TParams>\n | SubRouteRef<TParams>\n | ExternalRouteRef<TParams>,\n options?: RouteResolutionApiResolveOptions,\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(routeInfo: RouteInfo) {\n this.#delegate = new RouteResolver(\n routeInfo.routePaths,\n routeInfo.routeParents,\n routeInfo.routeObjects,\n this.routeBindings,\n this.appBasePath,\n );\n this.#routeObjects = routeInfo.routeObjects;\n\n return routeInfo;\n }\n\n getRouteObjects() {\n return this.#routeObjects;\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 * @public\n */\nexport function createSpecializedApp(options?: {\n features?: FrontendFeature[];\n config?: ConfigApi;\n bindRoutes?(context: { bind: CreateAppRouteBinder }): void;\n apis?: ApiHolder;\n extensionFactoryMiddleware?:\n | ExtensionFactoryMiddleware\n | ExtensionFactoryMiddleware[];\n flags?: { allowUnknownExtensionConfig?: boolean };\n pluginInfoResolver?: FrontendPluginInfoResolver;\n}): { apis: ApiHolder; tree: AppTree } {\n const config = options?.config ?? new ConfigReader({}, 'empty-config');\n const features = deduplicateFeatures(options?.features ?? []).map(\n createPluginInfoAttacher(config, options?.pluginInfoResolver),\n );\n\n const tree = resolveAppTree(\n 'root',\n resolveAppNodeSpecs({\n features,\n builtinExtensions: [\n resolveExtensionDefinition(Root, { namespace: 'root' }),\n ],\n parameters: readAppExtensionsConfig(config),\n forbidden: new Set(['root']),\n allowUnknownExtensionConfig: options?.flags?.allowUnknownExtensionConfig,\n }),\n );\n\n const factories = createApiFactories({ tree });\n const appBasePath = getBasePath(config);\n const appTreeApi = new AppTreeApiProxy(tree, appBasePath);\n const routeResolutionApi = new RouteResolutionApiProxy(\n resolveRouteBindings(\n options?.bindRoutes,\n config,\n collectRouteIds(features),\n ),\n appBasePath,\n );\n\n const appIdentityProxy = new AppIdentityProxy();\n const apis =\n options?.apis ??\n createApiHolder({\n factories,\n staticFactories: [\n createApiFactory(appTreeApiRef, appTreeApi),\n createApiFactory(configApiRef, config),\n createApiFactory(routeResolutionApiRef, routeResolutionApi),\n createApiFactory(identityApiRef, appIdentityProxy),\n ],\n });\n\n const featureFlagApi = apis.get(featureFlagsApiRef);\n if (featureFlagApi) {\n for (const feature of features) {\n if (OpaqueFrontendPlugin.isType(feature)) {\n OpaqueFrontendPlugin.toInternal(feature).featureFlags.forEach(flag =>\n featureFlagApi.registerFlag({\n name: flag.name,\n pluginId: feature.id,\n }),\n );\n }\n if (isInternalFrontendModule(feature)) {\n toInternalFrontendModule(feature).featureFlags.forEach(flag =>\n featureFlagApi.registerFlag({\n name: flag.name,\n pluginId: feature.pluginId,\n }),\n );\n }\n }\n }\n\n // Now instantiate the entire tree, which will skip anything that's already been instantiated\n instantiateAppNodeTree(\n tree.root,\n apis,\n mergeExtensionFactoryMiddleware(options?.extensionFactoryMiddleware),\n );\n\n const routeInfo = extractRouteInfoFromAppNode(tree.root);\n\n routeResolutionApi.initialize(routeInfo);\n appTreeApi.initialize(routeInfo);\n\n return { apis, tree };\n}\n\nfunction createApiFactories(options: { tree: AppTree }): AnyApiFactory[] {\n const emptyApiHolder = ApiRegistry.from([]);\n const factories = new Array<AnyApiFactory>();\n\n for (const apiNode of options.tree.root.edges.attachments.get('apis') ?? []) {\n instantiateAppNodeTree(apiNode, emptyApiHolder);\n const apiFactory = apiNode.instance?.getData(ApiBlueprint.dataRefs.factory);\n if (!apiFactory) {\n throw new Error(\n `No API factory found in for extension ${apiNode.spec.id}`,\n );\n }\n factories.push(apiFactory);\n }\n\n return factories;\n}\n\nfunction createApiHolder(options: {\n factories: AnyApiFactory[];\n staticFactories: AnyApiFactory[];\n}): ApiHolder {\n const factoryRegistry = new ApiFactoryRegistry();\n\n for (const factory of options.factories.slice().reverse()) {\n factoryRegistry.register('default', factory);\n }\n\n for (const factory of options.staticFactories) {\n factoryRegistry.register('static', factory);\n }\n\n ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis());\n\n return new ApiResolver(factoryRegistry);\n}\n\nfunction mergeExtensionFactoryMiddleware(\n middlewares?: ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[],\n): ExtensionFactoryMiddleware | undefined {\n if (!middlewares) {\n return undefined;\n }\n if (!Array.isArray(middlewares)) {\n return middlewares;\n }\n if (middlewares.length <= 1) {\n return middlewares[0];\n }\n return middlewares.reduce((prev, next) => {\n if (!prev || !next) {\n return prev ?? next;\n }\n return (orig, ctx) => {\n const internalExt = toInternalExtension(ctx.node.spec.extension);\n if (internalExt.version !== 'v2') {\n return orig();\n }\n return next(ctxOverrides => {\n return createExtensionDataContainer(\n prev(orig, {\n node: ctx.node,\n apis: ctx.apis,\n config: ctxOverrides?.config ?? ctx.config,\n }),\n );\n }, ctx);\n };\n });\n}\n"],"names":[],"mappings":"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"}
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 { ConfigReader } from '@backstage/config';\nimport {\n ApiBlueprint,\n AppTree,\n AppTreeApi,\n appTreeApiRef,\n RouteRef,\n ExternalRouteRef,\n SubRouteRef,\n AnyRouteRefParams,\n RouteFunc,\n RouteResolutionApi,\n createApiFactory,\n routeResolutionApiRef,\n AppNode,\n ExtensionFactoryMiddleware,\n FrontendFeature,\n} from '@backstage/frontend-plugin-api';\nimport {\n AnyApiFactory,\n ApiHolder,\n ConfigApi,\n configApiRef,\n featureFlagsApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\nimport { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api';\nimport {\n createExtensionDataContainer,\n OpaqueFrontendPlugin,\n} from '@internal/frontend';\n\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n resolveExtensionDefinition,\n toInternalExtension,\n} from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\n\nimport { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode';\n\nimport { CreateAppRouteBinder } from '../routing';\nimport { RouteResolver } from '../routing/RouteResolver';\nimport { resolveRouteBindings } from '../routing/resolveRouteBindings';\nimport { collectRouteIds } from '../routing/collectRouteIds';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport {\n toInternalFrontendModule,\n isInternalFrontendModule,\n} from '../../../frontend-plugin-api/src/wiring/createFrontendModule';\nimport { getBasePath } from '../routing/getBasePath';\nimport { Root } from '../extensions/Root';\nimport { resolveAppTree } from '../tree/resolveAppTree';\nimport { resolveAppNodeSpecs } from '../tree/resolveAppNodeSpecs';\nimport { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig';\nimport { instantiateAppNodeTree } from '../tree/instantiateAppNodeTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';\nimport { BackstageRouteObject } from '../routing/types';\nimport { RouteInfo } from './types';\nimport { matchRoutes } from 'react-router-dom';\nimport {\n createPluginInfoAttacher,\n FrontendPluginInfoResolver,\n} from './createPluginInfoAttacher';\nimport { createRouteAliasResolver } from '../routing/RouteAliasResolver';\n\nfunction deduplicateFeatures(\n allFeatures: FrontendFeature[],\n): FrontendFeature[] {\n // Start by removing duplicates by reference\n const features = Array.from(new Set(allFeatures));\n\n // Plugins are deduplicated by ID, last one wins\n const seenIds = new Set<string>();\n return features\n .reverse()\n .filter(feature => {\n if (!OpaqueFrontendPlugin.isType(feature)) {\n return true;\n }\n if (seenIds.has(feature.id)) {\n return false;\n }\n seenIds.add(feature.id);\n return true;\n })\n .reverse();\n}\n\n// Helps delay callers from reaching out to the API before the app tree has been materialized\nclass AppTreeApiProxy implements AppTreeApi {\n #routeInfo?: RouteInfo;\n\n constructor(\n private readonly tree: AppTree,\n private readonly appBasePath: string,\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\n let path = routePath;\n if (path.startsWith(this.appBasePath)) {\n path = path.slice(this.appBasePath.length);\n }\n\n const matchedRoutes = matchRoutes(this.#routeInfo!.routeObjects, path);\n\n const matchedAppNodes =\n matchedRoutes\n ?.filter(routeObj => !!routeObj.route.appNode)\n .map(routeObj => routeObj.route.appNode!) || [];\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\nclass RouteResolutionApiProxy implements RouteResolutionApi {\n #delegate: RouteResolutionApi | undefined;\n #routeObjects: BackstageRouteObject[] | undefined;\n\n constructor(\n private readonly routeBindings: Map<\n ExternalRouteRef,\n RouteRef | SubRouteRef\n >,\n private readonly appBasePath: string,\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(routeInfo: RouteInfo) {\n this.#delegate = new RouteResolver(\n routeInfo.routePaths,\n routeInfo.routeParents,\n routeInfo.routeObjects,\n this.routeBindings,\n this.appBasePath,\n routeInfo.routeAliasResolver,\n );\n this.#routeObjects = routeInfo.routeObjects;\n\n return routeInfo;\n }\n\n getRouteObjects() {\n return this.#routeObjects;\n }\n}\n\n/**\n * Options for {@link createSpecializedApp}.\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 * A replacement API holder implementation to use.\n *\n * By default, a new API holder will be constructed automatically based on\n * the other inputs. If you pass in a custom one here, none of that\n * automation will take place - so you will have to take care to supply all\n * those APIs yourself.\n */\n apis?: ApiHolder;\n\n /**\n * If set to true, the system will silently accept and move on if\n * encountering config for extensions that do not exist. The default is to\n * reject such config to help catch simple mistakes.\n *\n * This flag can be useful in some scenarios where you have a dynamic set of\n * extensions enabled at different times, but also increases the risk of\n * accidentally missing e.g. simple typos in your config.\n */\n allowUnknownExtensionConfig?: boolean;\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 * @public\n */\nexport function createSpecializedApp(options?: CreateSpecializedAppOptions): {\n apis: ApiHolder;\n tree: AppTree;\n} {\n const config = options?.config ?? new ConfigReader({}, 'empty-config');\n const features = deduplicateFeatures(options?.features ?? []).map(\n createPluginInfoAttacher(config, options?.advanced?.pluginInfoResolver),\n );\n\n const tree = resolveAppTree(\n 'root',\n resolveAppNodeSpecs({\n features,\n builtinExtensions: [\n resolveExtensionDefinition(Root, { namespace: 'root' }),\n ],\n parameters: readAppExtensionsConfig(config),\n forbidden: new Set(['root']),\n allowUnknownExtensionConfig:\n options?.advanced?.allowUnknownExtensionConfig,\n }),\n );\n\n const factories = createApiFactories({ tree });\n const appBasePath = getBasePath(config);\n const appTreeApi = new AppTreeApiProxy(tree, appBasePath);\n\n const routeRefsById = collectRouteIds(features);\n const routeResolutionApi = new RouteResolutionApiProxy(\n resolveRouteBindings(options?.bindRoutes, config, routeRefsById),\n appBasePath,\n );\n\n const appIdentityProxy = new AppIdentityProxy();\n const apis =\n options?.advanced?.apis ??\n createApiHolder({\n factories,\n staticFactories: [\n createApiFactory(appTreeApiRef, appTreeApi),\n createApiFactory(configApiRef, config),\n createApiFactory(routeResolutionApiRef, routeResolutionApi),\n createApiFactory(identityApiRef, appIdentityProxy),\n ],\n });\n\n const featureFlagApi = apis.get(featureFlagsApiRef);\n if (featureFlagApi) {\n for (const feature of features) {\n if (OpaqueFrontendPlugin.isType(feature)) {\n OpaqueFrontendPlugin.toInternal(feature).featureFlags.forEach(flag =>\n featureFlagApi.registerFlag({\n name: flag.name,\n pluginId: feature.id,\n }),\n );\n }\n if (isInternalFrontendModule(feature)) {\n toInternalFrontendModule(feature).featureFlags.forEach(flag =>\n featureFlagApi.registerFlag({\n name: flag.name,\n pluginId: feature.pluginId,\n }),\n );\n }\n }\n }\n\n // Now instantiate the entire tree, which will skip anything that's already been instantiated\n instantiateAppNodeTree(\n tree.root,\n apis,\n mergeExtensionFactoryMiddleware(\n options?.advanced?.extensionFactoryMiddleware,\n ),\n );\n\n const routeInfo = extractRouteInfoFromAppNode(\n tree.root,\n createRouteAliasResolver(routeRefsById),\n );\n\n routeResolutionApi.initialize(routeInfo);\n appTreeApi.initialize(routeInfo);\n\n return { apis, tree };\n}\n\nfunction createApiFactories(options: { tree: AppTree }): AnyApiFactory[] {\n const emptyApiHolder = ApiRegistry.from([]);\n const factories = new Array<AnyApiFactory>();\n\n for (const apiNode of options.tree.root.edges.attachments.get('apis') ?? []) {\n instantiateAppNodeTree(apiNode, emptyApiHolder);\n const apiFactory = apiNode.instance?.getData(ApiBlueprint.dataRefs.factory);\n if (!apiFactory) {\n throw new Error(\n `No API factory found in for extension ${apiNode.spec.id}`,\n );\n }\n factories.push(apiFactory);\n }\n\n return factories;\n}\n\nfunction createApiHolder(options: {\n factories: AnyApiFactory[];\n staticFactories: AnyApiFactory[];\n}): ApiHolder {\n const factoryRegistry = new ApiFactoryRegistry();\n\n for (const factory of options.factories.slice().reverse()) {\n factoryRegistry.register('default', factory);\n }\n\n for (const factory of options.staticFactories) {\n factoryRegistry.register('static', factory);\n }\n\n ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis());\n\n return new ApiResolver(factoryRegistry);\n}\n\nfunction mergeExtensionFactoryMiddleware(\n middlewares?: ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[],\n): ExtensionFactoryMiddleware | undefined {\n if (!middlewares) {\n return undefined;\n }\n if (!Array.isArray(middlewares)) {\n return middlewares;\n }\n if (middlewares.length <= 1) {\n return middlewares[0];\n }\n return middlewares.reduce((prev, next) => {\n if (!prev || !next) {\n return prev ?? next;\n }\n return (orig, ctx) => {\n const internalExt = toInternalExtension(ctx.node.spec.extension);\n if (internalExt.version !== 'v2') {\n return orig();\n }\n return next(ctxOverrides => {\n return createExtensionDataContainer(\n prev(orig, {\n node: ctx.node,\n apis: ctx.apis,\n config: ctxOverrides?.config ?? ctx.config,\n }),\n 'extension factory middleware',\n );\n }, ctx);\n };\n });\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/frontend-app-api",
3
- "version": "0.11.5-next.1",
3
+ "version": "0.12.0-next.3",
4
4
  "backstage": {
5
5
  "role": "web-library"
6
6
  },
@@ -36,16 +36,17 @@
36
36
  "@backstage/core-app-api": "1.18.0",
37
37
  "@backstage/core-plugin-api": "1.10.9",
38
38
  "@backstage/errors": "1.2.7",
39
- "@backstage/frontend-defaults": "0.2.5-next.1",
40
- "@backstage/frontend-plugin-api": "0.11.0-next.0",
39
+ "@backstage/frontend-defaults": "0.3.0-next.3",
40
+ "@backstage/frontend-plugin-api": "0.11.0-next.2",
41
41
  "@backstage/types": "1.2.1",
42
42
  "@backstage/version-bridge": "1.0.11",
43
43
  "lodash": "^4.17.21",
44
44
  "zod": "^3.22.4"
45
45
  },
46
46
  "devDependencies": {
47
- "@backstage/cli": "0.33.2-next.0",
48
- "@backstage/plugin-app": "0.2.0-next.0",
47
+ "@backstage/cli": "0.34.0-next.2",
48
+ "@backstage/frontend-test-utils": "0.3.5-next.2",
49
+ "@backstage/plugin-app": "0.2.0-next.2",
49
50
  "@backstage/test-utils": "1.7.11-next.0",
50
51
  "@testing-library/jest-dom": "^6.0.0",
51
52
  "@testing-library/react": "^16.0.0",