@backstage/core-compat-api 0.3.7-next.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,86 @@
1
1
  # @backstage/core-compat-api
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8250ffe: **BREAKING**: Dropped support for the removed opaque `@backstage/ExtensionOverrides` and `@backstage/BackstagePlugin` types.
8
+
9
+ ### Patch Changes
10
+
11
+ - cbe6177: Improved route path normalization when converting existing route elements in `converLegacyApp`, for example handling trailing `/*` in paths.
12
+ - d34e0e5: Added a new `convertLegacyAppOptions` helper that converts many of the options passed to `createApp` in the old frontend system to a module with app overrides for the new system. The supported options are `apis`, `icons`, `plugins`, `components`, and `themes`.
13
+
14
+ For example, given the following options for the old `createApp`:
15
+
16
+ ```ts
17
+ import { createApp } from '@backstage/app-deafults';
18
+
19
+ const app = createApp({
20
+ apis,
21
+ plugins,
22
+ icons: {
23
+ custom: MyIcon,
24
+ },
25
+ components: {
26
+ SignInPage: MySignInPage,
27
+ },
28
+ themes: [myTheme],
29
+ });
30
+ ```
31
+
32
+ They can be converted to the new system like this:
33
+
34
+ ```ts
35
+ import { createApp } from '@backstage/frontend-deafults';
36
+ import { convertLegacyAppOptions } from '@backstage/core-compat-api';
37
+
38
+ const app = createApp({
39
+ features: [
40
+ convertLegacyAppOptions({
41
+ apis,
42
+ plugins,
43
+ icons: {
44
+ custom: MyIcon,
45
+ },
46
+ components: {
47
+ SignInPage: MySignInPage,
48
+ },
49
+ themes: [myTheme],
50
+ }),
51
+ ],
52
+ });
53
+ ```
54
+
55
+ - e7fab55: Added the `entityPage` option to `convertLegacyApp`, which you can read more about in the [app migration docs](https://backstage.io/docs/frontend-system/building-apps/migrating#entity-pages).
56
+ - 18faf65: The `convertLegacyApp` has received the following changes:
57
+
58
+ - `null` routes will now be ignored.
59
+ - Converted routes no longer need to belong to a plugin, falling back to a `converted-orphan-routes` plugin instead.
60
+ - The generate layout override extension is now properly attached to the `app/root` extension.
61
+ - Converted root elements are now automatically wrapped with `compatWrapper`.
62
+
63
+ - Updated dependencies
64
+ - @backstage/core-plugin-api@1.10.5
65
+ - @backstage/frontend-plugin-api@0.10.0
66
+ - @backstage/plugin-catalog-react@1.16.0
67
+ - @backstage/version-bridge@1.0.11
68
+
69
+ ## 0.4.0-next.2
70
+
71
+ ### Minor Changes
72
+
73
+ - 8250ffe: **BREAKING**: Dropped support for the removed opaque `@backstage/ExtensionOverrides` and `@backstage/BackstagePlugin` types.
74
+
75
+ ### Patch Changes
76
+
77
+ - e7fab55: Added the `entityPage` option to `convertLegacyApp`, which you can read more about in the [app migration docs](https://backstage.io/docs/frontend-system/building-apps/migrating#entity-pages).
78
+ - Updated dependencies
79
+ - @backstage/frontend-plugin-api@0.10.0-next.2
80
+ - @backstage/plugin-catalog-react@1.16.0-next.2
81
+ - @backstage/core-plugin-api@1.10.4
82
+ - @backstage/version-bridge@1.0.11
83
+
3
84
  ## 0.3.7-next.1
4
85
 
5
86
  ### Patch Changes
@@ -0,0 +1,164 @@
1
+ import { getComponentData } from '@backstage/core-plugin-api';
2
+ import React from 'react';
3
+ import { EntityCardBlueprint, EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha';
4
+ import { normalizeRoutePath } from './normalizeRoutePath.esm.js';
5
+
6
+ const ENTITY_SWITCH_KEY = "core.backstage.entitySwitch";
7
+ const ENTITY_ROUTE_KEY = "plugin.catalog.entityLayoutRoute";
8
+ function allFilters(...ifs) {
9
+ const filtered = ifs.filter(Boolean);
10
+ if (!filtered.length) {
11
+ return void 0;
12
+ }
13
+ if (filtered.length === 1) {
14
+ return filtered[0];
15
+ }
16
+ return (entity, ctx) => filtered.every((ifFunc) => ifFunc(entity, ctx));
17
+ }
18
+ function anyFilters(...ifs) {
19
+ const filtered = ifs.filter(Boolean);
20
+ if (!filtered.length) {
21
+ return void 0;
22
+ }
23
+ if (filtered.length === 1) {
24
+ return filtered[0];
25
+ }
26
+ return (entity, ctx) => filtered.some((ifFunc) => ifFunc(entity, ctx));
27
+ }
28
+ function invertFilter(ifFunc) {
29
+ if (!ifFunc) {
30
+ return () => true;
31
+ }
32
+ return (entity, ctx) => !ifFunc(entity, ctx);
33
+ }
34
+ function collectEntityPageContents(entityPageElement, context) {
35
+ let cardCounter = 1;
36
+ let routeCounter = 1;
37
+ function traverse(element, parentFilter) {
38
+ if (!React.isValidElement(element)) {
39
+ return;
40
+ }
41
+ const pageNode = maybeParseEntityPageNode(element);
42
+ if (pageNode) {
43
+ if (pageNode.type === "route") {
44
+ const mergedIf = allFilters(parentFilter, pageNode.if);
45
+ if (pageNode.path === "/") {
46
+ context.discoverExtension(
47
+ EntityCardBlueprint.makeWithOverrides({
48
+ name: `discovered-${cardCounter++}`,
49
+ factory(originalFactory, { apis }) {
50
+ return originalFactory({
51
+ type: "content",
52
+ filter: mergedIf && ((entity) => mergedIf(entity, { apis })),
53
+ loader: () => Promise.resolve(pageNode.children)
54
+ });
55
+ }
56
+ })
57
+ );
58
+ } else {
59
+ const name = `discovered-${routeCounter++}`;
60
+ context.discoverExtension(
61
+ EntityContentBlueprint.makeWithOverrides({
62
+ name,
63
+ factory(originalFactory, { apis }) {
64
+ return originalFactory({
65
+ defaultPath: normalizeRoutePath(pageNode.path),
66
+ defaultTitle: pageNode.title,
67
+ filter: mergedIf && ((entity) => mergedIf(entity, { apis })),
68
+ loader: () => Promise.resolve(pageNode.children)
69
+ });
70
+ }
71
+ }),
72
+ getComponentData(
73
+ pageNode.children,
74
+ "core.plugin"
75
+ )
76
+ );
77
+ }
78
+ }
79
+ if (pageNode.type === "switch") {
80
+ if (pageNode.renderMultipleMatches === "all") {
81
+ for (const entityCase of pageNode.cases) {
82
+ traverse(
83
+ entityCase.children,
84
+ allFilters(parentFilter, entityCase.if)
85
+ );
86
+ }
87
+ } else {
88
+ let previousIf = () => false;
89
+ for (const entityCase of pageNode.cases) {
90
+ const didNotMatchEarlier = invertFilter(previousIf);
91
+ traverse(
92
+ entityCase.children,
93
+ allFilters(parentFilter, entityCase.if, didNotMatchEarlier)
94
+ );
95
+ previousIf = anyFilters(previousIf, entityCase.if);
96
+ }
97
+ }
98
+ }
99
+ return;
100
+ }
101
+ React.Children.forEach(
102
+ element.props?.children,
103
+ (child) => {
104
+ traverse(child, parentFilter);
105
+ }
106
+ );
107
+ }
108
+ traverse(entityPageElement);
109
+ }
110
+ function wrapAsyncEntityFilter(asyncFilter) {
111
+ if (!asyncFilter) {
112
+ return asyncFilter;
113
+ }
114
+ let loggedError = false;
115
+ return (entity, ctx) => {
116
+ const result = asyncFilter(entity, ctx);
117
+ if (result && typeof result === "object" && "then" in result) {
118
+ if (!loggedError) {
119
+ console.error(
120
+ `collectEntityPageContents does not support async entity filters, skipping filter ${asyncFilter}`
121
+ );
122
+ loggedError = true;
123
+ }
124
+ return false;
125
+ }
126
+ return result;
127
+ };
128
+ }
129
+ function maybeParseEntityPageNode(element) {
130
+ if (getComponentData(element, ENTITY_ROUTE_KEY)) {
131
+ const props = element.props;
132
+ return {
133
+ type: "route",
134
+ path: props.path,
135
+ title: props.title,
136
+ if: props.if,
137
+ children: props.children
138
+ };
139
+ }
140
+ const parentProps = element.props;
141
+ const children = React.Children.toArray(parentProps?.children);
142
+ if (!children.length) {
143
+ return void 0;
144
+ }
145
+ const cases = [];
146
+ for (const child of children) {
147
+ if (!getComponentData(child, ENTITY_SWITCH_KEY)) {
148
+ return void 0;
149
+ }
150
+ const props = child.props;
151
+ cases.push({
152
+ if: wrapAsyncEntityFilter(props.if),
153
+ children: props.children
154
+ });
155
+ }
156
+ return {
157
+ type: "switch",
158
+ cases,
159
+ renderMultipleMatches: parentProps?.renderMultipleMatches ?? "first"
160
+ };
161
+ }
162
+
163
+ export { collectEntityPageContents };
164
+ //# sourceMappingURL=collectEntityPageContents.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collectEntityPageContents.esm.js","sources":["../src/collectEntityPageContents.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 {\n ApiHolder,\n getComponentData,\n BackstagePlugin as LegacyBackstagePlugin,\n} from '@backstage/core-plugin-api';\nimport { ExtensionDefinition } from '@backstage/frontend-plugin-api';\nimport React from 'react';\nimport {\n EntityCardBlueprint,\n EntityContentBlueprint,\n} from '@backstage/plugin-catalog-react/alpha';\nimport { normalizeRoutePath } from './normalizeRoutePath';\n\nconst ENTITY_SWITCH_KEY = 'core.backstage.entitySwitch';\nconst ENTITY_ROUTE_KEY = 'plugin.catalog.entityLayoutRoute';\n\n// Placeholder to make sure internal types here are consitent\ntype Entity = { apiVersion: string; kind: string };\n\ntype EntityFilter = (entity: Entity, ctx: { apis: ApiHolder }) => boolean;\ntype AsyncEntityFilter = (\n entity: Entity,\n context: { apis: ApiHolder },\n) => boolean | Promise<boolean>;\n\nfunction allFilters(\n ...ifs: (EntityFilter | undefined)[]\n): EntityFilter | undefined {\n const filtered = ifs.filter(Boolean) as EntityFilter[];\n if (!filtered.length) {\n return undefined;\n }\n if (filtered.length === 1) {\n return filtered[0];\n }\n return (entity, ctx) => filtered.every(ifFunc => ifFunc(entity, ctx));\n}\n\nfunction anyFilters(\n ...ifs: (EntityFilter | undefined)[]\n): EntityFilter | undefined {\n const filtered = ifs.filter(Boolean) as EntityFilter[];\n if (!filtered.length) {\n return undefined;\n }\n if (filtered.length === 1) {\n return filtered[0];\n }\n return (entity, ctx) => filtered.some(ifFunc => ifFunc(entity, ctx));\n}\n\nfunction invertFilter(ifFunc?: EntityFilter): EntityFilter {\n if (!ifFunc) {\n return () => true;\n }\n return (entity, ctx) => !ifFunc(entity, ctx);\n}\n\nexport function collectEntityPageContents(\n entityPageElement: React.JSX.Element,\n context: {\n discoverExtension(\n extension: ExtensionDefinition,\n plugin?: LegacyBackstagePlugin,\n ): void;\n },\n) {\n let cardCounter = 1;\n let routeCounter = 1;\n\n function traverse(element: React.ReactNode, parentFilter?: EntityFilter) {\n if (!React.isValidElement(element)) {\n return;\n }\n\n const pageNode = maybeParseEntityPageNode(element);\n if (pageNode) {\n if (pageNode.type === 'route') {\n const mergedIf = allFilters(parentFilter, pageNode.if);\n\n if (pageNode.path === '/') {\n context.discoverExtension(\n EntityCardBlueprint.makeWithOverrides({\n name: `discovered-${cardCounter++}`,\n factory(originalFactory, { apis }) {\n return originalFactory({\n type: 'content',\n filter: mergedIf && (entity => mergedIf(entity, { apis })),\n loader: () => Promise.resolve(pageNode.children),\n });\n },\n }),\n );\n } else {\n const name = `discovered-${routeCounter++}`;\n\n context.discoverExtension(\n EntityContentBlueprint.makeWithOverrides({\n name,\n factory(originalFactory, { apis }) {\n return originalFactory({\n defaultPath: normalizeRoutePath(pageNode.path),\n defaultTitle: pageNode.title,\n filter: mergedIf && (entity => mergedIf(entity, { apis })),\n loader: () => Promise.resolve(pageNode.children),\n });\n },\n }),\n getComponentData<LegacyBackstagePlugin>(\n pageNode.children,\n 'core.plugin',\n ),\n );\n }\n }\n if (pageNode.type === 'switch') {\n if (pageNode.renderMultipleMatches === 'all') {\n for (const entityCase of pageNode.cases) {\n traverse(\n entityCase.children,\n allFilters(parentFilter, entityCase.if),\n );\n }\n } else {\n let previousIf: EntityFilter = () => false;\n for (const entityCase of pageNode.cases) {\n const didNotMatchEarlier = invertFilter(previousIf);\n traverse(\n entityCase.children,\n allFilters(parentFilter, entityCase.if, didNotMatchEarlier),\n );\n previousIf = anyFilters(previousIf, entityCase.if)!;\n }\n }\n }\n return;\n }\n\n React.Children.forEach(\n (element.props as { children?: React.ReactNode })?.children,\n child => {\n traverse(child, parentFilter);\n },\n );\n }\n\n traverse(entityPageElement);\n}\n\ntype EntityRoute = {\n type: 'route';\n path: string;\n title: string;\n if?: EntityFilter;\n children: JSX.Element;\n};\n\ntype EntitySwitchCase = {\n if?: EntityFilter;\n children: React.ReactNode;\n};\n\ntype EntitySwitch = {\n type: 'switch';\n cases: EntitySwitchCase[];\n renderMultipleMatches: 'first' | 'all';\n};\n\nfunction wrapAsyncEntityFilter(\n asyncFilter?: AsyncEntityFilter,\n): EntityFilter | undefined {\n if (!asyncFilter) {\n return asyncFilter;\n }\n let loggedError = false;\n return (entity, ctx) => {\n const result = asyncFilter(entity, ctx);\n if (result && typeof result === 'object' && 'then' in result) {\n if (!loggedError) {\n // eslint-disable-next-line no-console\n console.error(\n `collectEntityPageContents does not support async entity filters, skipping filter ${asyncFilter}`,\n );\n loggedError = true;\n }\n return false;\n }\n return result;\n };\n}\n\nfunction maybeParseEntityPageNode(\n element: React.JSX.Element,\n): EntityRoute | EntitySwitch | undefined {\n if (getComponentData(element, ENTITY_ROUTE_KEY)) {\n const props = element.props as EntityRoute;\n return {\n type: 'route',\n path: props.path,\n title: props.title,\n if: props.if,\n children: props.children,\n };\n }\n\n const parentProps = element.props as {\n children?: React.ReactNode;\n renderMultipleMatches?: 'first' | 'all';\n };\n\n const children = React.Children.toArray(parentProps?.children);\n if (!children.length) {\n return undefined;\n }\n\n const cases = [];\n for (const child of children) {\n if (!getComponentData(child, ENTITY_SWITCH_KEY)) {\n return undefined;\n }\n const props = (child as { props: EntitySwitchCase }).props;\n\n cases.push({\n if: wrapAsyncEntityFilter(props.if),\n children: props.children,\n });\n }\n return {\n type: 'switch',\n cases,\n renderMultipleMatches: parentProps?.renderMultipleMatches ?? 'first',\n };\n}\n"],"names":[],"mappings":"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"}
@@ -1,9 +1,11 @@
1
1
  import { createPlugin, getComponentData } from '@backstage/core-plugin-api';
2
- import { PageBlueprint, createExtensionInput, coreExtensionData, createFrontendPlugin, ApiBlueprint, createExtension } from '@backstage/frontend-plugin-api';
2
+ import { PageBlueprint, createExtensionInput, coreExtensionData, createFrontendModule, createFrontendPlugin, ApiBlueprint, createExtension } from '@backstage/frontend-plugin-api';
3
3
  import React, { Children, isValidElement } from 'react';
4
4
  import { Route, Routes } from 'react-router-dom';
5
5
  import { convertLegacyRouteRef, convertLegacyRouteRefs } from './convertLegacyRouteRef.esm.js';
6
6
  import { compatWrapper } from './compatWrapper/compatWrapper.esm.js';
7
+ import { collectEntityPageContents } from './collectEntityPageContents.esm.js';
8
+ import { normalizeRoutePath } from './normalizeRoutePath.esm.js';
7
9
 
8
10
  function makeRoutingShimExtension(options) {
9
11
  const { name, parentExtensionId, routePath, routeRef } = options;
@@ -67,7 +69,8 @@ function visitRouteChildren(options) {
67
69
  });
68
70
  });
69
71
  }
70
- function collectLegacyRoutes(flatRoutesElement) {
72
+ function collectLegacyRoutes(flatRoutesElement, entityPage) {
73
+ const output = new Array();
71
74
  const pluginExtensions = /* @__PURE__ */ new Map();
72
75
  const getUniqueName = /* @__PURE__ */ (() => {
73
76
  let currentIndex = 1;
@@ -124,7 +127,7 @@ function collectLegacyRoutes(flatRoutesElement) {
124
127
  },
125
128
  factory(originalFactory, { inputs: _inputs }) {
126
129
  return originalFactory({
127
- defaultPath: path[0] === "/" ? path.slice(1) : path,
130
+ defaultPath: normalizeRoutePath(path),
128
131
  routeRef: routeRef ? convertLegacyRouteRef(routeRef) : void 0,
129
132
  loader: async () => compatWrapper(
130
133
  route.props.children ? /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: "*", element: routeElement }, /* @__PURE__ */ React.createElement(Route, { path: "*", element: route.props.children }))) : routeElement
@@ -145,23 +148,58 @@ function collectLegacyRoutes(flatRoutesElement) {
145
148
  });
146
149
  }
147
150
  );
148
- return Array.from(pluginExtensions).map(
149
- ([plugin, extensions]) => createFrontendPlugin({
150
- id: plugin.getId(),
151
- extensions: [
152
- ...extensions,
153
- ...Array.from(plugin.getApis()).map(
154
- (factory) => ApiBlueprint.make({
155
- name: factory.api.id,
156
- params: { factory }
157
- })
158
- )
159
- ],
160
- routes: convertLegacyRouteRefs(plugin.routes ?? {}),
161
- externalRoutes: convertLegacyRouteRefs(plugin.externalRoutes ?? {})
162
- })
163
- );
151
+ if (entityPage) {
152
+ collectEntityPageContents(entityPage, {
153
+ discoverExtension(extension, plugin) {
154
+ if (!plugin || plugin.getId() === "catalog") {
155
+ getPluginExtensions(orphanRoutesPlugin).push(extension);
156
+ } else {
157
+ getPluginExtensions(plugin).push(extension);
158
+ }
159
+ }
160
+ });
161
+ const extensions = new Array();
162
+ visitRouteChildren({
163
+ children: entityPage,
164
+ parentExtensionId: `page:catalog/entity`,
165
+ context: {
166
+ pluginId: "catalog",
167
+ extensions,
168
+ getUniqueName,
169
+ discoverPlugin(plugin) {
170
+ if (plugin.getId() !== "catalog") {
171
+ getPluginExtensions(plugin);
172
+ }
173
+ }
174
+ }
175
+ });
176
+ output.push(
177
+ createFrontendModule({
178
+ pluginId: "catalog",
179
+ extensions
180
+ })
181
+ );
182
+ }
183
+ for (const [plugin, extensions] of pluginExtensions) {
184
+ output.push(
185
+ createFrontendPlugin({
186
+ id: plugin.getId(),
187
+ extensions: [
188
+ ...extensions,
189
+ ...Array.from(plugin.getApis()).map(
190
+ (factory) => ApiBlueprint.make({
191
+ name: factory.api.id,
192
+ params: { factory }
193
+ })
194
+ )
195
+ ],
196
+ routes: convertLegacyRouteRefs(plugin.routes ?? {}),
197
+ externalRoutes: convertLegacyRouteRefs(plugin.externalRoutes ?? {})
198
+ })
199
+ );
200
+ }
201
+ return output;
164
202
  }
165
203
 
166
- export { collectLegacyRoutes };
204
+ export { collectLegacyRoutes, visitRouteChildren };
167
205
  //# sourceMappingURL=collectLegacyRoutes.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collectLegacyRoutes.esm.js","sources":["../src/collectLegacyRoutes.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AnyRouteRefParams,\n BackstagePlugin as LegacyBackstagePlugin,\n RouteRef,\n createPlugin,\n getComponentData,\n} from '@backstage/core-plugin-api';\nimport {\n FrontendPlugin,\n ExtensionDefinition,\n coreExtensionData,\n createExtension,\n createExtensionInput,\n createFrontendPlugin,\n ApiBlueprint,\n PageBlueprint,\n} from '@backstage/frontend-plugin-api';\nimport React, { Children, ReactNode, isValidElement } from 'react';\nimport { Route, Routes } from 'react-router-dom';\nimport {\n convertLegacyRouteRef,\n convertLegacyRouteRefs,\n} from './convertLegacyRouteRef';\nimport { compatWrapper } from './compatWrapper';\n\n/*\n\n# Legacy interoperability\n\nUse-cases (prioritized):\n 1. Slowly migrate over an existing app to DI, piece by piece\n 2. Use a legacy plugin in a new DI app\n 3. Use DI in an existing legacy app\n\nStarting point: use-case #1\n\nPotential solutions:\n 1. Codemods (we're not considering this for now)\n 2. Legacy apps are migrated bottom-up, i.e. keep legacy root, replace pages with DI\n 3. Legacy apps are migrated top-down i.e. switch out base to DI, legacy adapter allows for usage of existing app structure\n\nChosen path: #3\n\nExisting tasks:\n - Adopters can migrate their existing app gradually (~4)\n - Example-app uses legacy base with DI adapters\n - Create an API that lets you inject DI into existing apps - working assumption is that this is enough\n - Adopters can use legacy plugins in DI through adapters (~8)\n - App-next uses DI base with legacy adapters\n - Create a legacy adapter that is able to take an existing extension tree\n\n*/\n\n// Creates a shim extension whose purpose is to build up the tree (anchored at\n// the root page) of paths/routeRefs so that the app can bind them properly.\nfunction makeRoutingShimExtension(options: {\n name: string;\n parentExtensionId: string;\n routePath?: string;\n routeRef?: RouteRef;\n}) {\n const { name, parentExtensionId, routePath, routeRef } = options;\n return createExtension({\n kind: 'routing-shim',\n name,\n attachTo: { id: parentExtensionId, input: 'childRoutingShims' },\n inputs: {\n childRoutingShims: createExtensionInput([\n coreExtensionData.routePath.optional(),\n coreExtensionData.routeRef.optional(),\n ]),\n },\n output: [\n coreExtensionData.routePath.optional(),\n coreExtensionData.routeRef.optional(),\n ],\n *factory() {\n if (routePath !== undefined) {\n yield coreExtensionData.routePath(routePath);\n }\n\n if (routeRef) {\n yield coreExtensionData.routeRef(convertLegacyRouteRef(routeRef));\n }\n },\n });\n}\n\nfunction visitRouteChildren(options: {\n children: ReactNode;\n parentExtensionId: string;\n context: {\n pluginId: string;\n extensions: ExtensionDefinition[];\n getUniqueName: () => string;\n discoverPlugin: (plugin: LegacyBackstagePlugin) => void;\n };\n}): void {\n const { children, parentExtensionId, context } = options;\n const { pluginId, extensions, getUniqueName, discoverPlugin } = context;\n\n Children.forEach(children, node => {\n if (!isValidElement(node)) {\n return;\n }\n\n const plugin = getComponentData<LegacyBackstagePlugin>(node, 'core.plugin');\n const routeRef = getComponentData<RouteRef<AnyRouteRefParams>>(\n node,\n 'core.mountPoint',\n );\n const routePath: string | undefined = node.props?.path;\n\n if (plugin) {\n // We just mark the plugin as discovered, but don't change the context\n discoverPlugin(plugin);\n }\n\n let nextParentExtensionId = parentExtensionId;\n if (routeRef || routePath) {\n const nextParentExtensionName = getUniqueName();\n nextParentExtensionId = `routing-shim:${pluginId}/${nextParentExtensionName}`;\n extensions.push(\n makeRoutingShimExtension({\n name: nextParentExtensionName,\n parentExtensionId,\n routePath,\n routeRef,\n }),\n );\n }\n\n visitRouteChildren({\n children: node.props.children,\n parentExtensionId: nextParentExtensionId,\n context,\n });\n });\n}\n\n/** @internal */\nexport function collectLegacyRoutes(\n flatRoutesElement: JSX.Element,\n): FrontendPlugin[] {\n const pluginExtensions = new Map<\n LegacyBackstagePlugin,\n ExtensionDefinition[]\n >();\n\n const getUniqueName = (() => {\n let currentIndex = 1;\n return () => String(currentIndex++);\n })();\n\n // Placeholder plugin for any routes that don't belong to a plugin\n const orphanRoutesPlugin = createPlugin({ id: 'converted-orphan-routes' });\n\n const getPluginExtensions = (plugin: LegacyBackstagePlugin) => {\n let extensions = pluginExtensions.get(plugin);\n if (!extensions) {\n extensions = [];\n pluginExtensions.set(plugin, extensions);\n }\n return extensions;\n };\n\n React.Children.forEach(\n flatRoutesElement.props.children,\n (route: ReactNode) => {\n if (route === null) {\n return;\n }\n // TODO(freben): Handle feature flag and permissions framework wrapper elements\n if (!React.isValidElement(route)) {\n throw new Error(\n `Invalid element inside FlatRoutes, expected Route but found element of type ${typeof route}.`,\n );\n }\n if (route.type !== Route) {\n throw new Error(\n `Invalid element inside FlatRoutes, expected Route but found ${route.type}.`,\n );\n }\n const routeElement = route.props.element;\n const path: string | undefined = route.props.path;\n const plugin =\n getComponentData<LegacyBackstagePlugin>(routeElement, 'core.plugin') ??\n orphanRoutesPlugin;\n const routeRef = getComponentData<RouteRef>(\n routeElement,\n 'core.mountPoint',\n );\n if (path === undefined) {\n throw new Error(\n `Route element inside FlatRoutes had no path prop value given`,\n );\n }\n\n const extensions = getPluginExtensions(plugin);\n const pageExtensionName = extensions.length ? getUniqueName() : undefined;\n const pageExtensionId = `page:${plugin.getId()}${\n pageExtensionName ? `/${pageExtensionName}` : pageExtensionName\n }`;\n\n extensions.push(\n PageBlueprint.makeWithOverrides({\n name: pageExtensionName,\n inputs: {\n childRoutingShims: createExtensionInput([\n coreExtensionData.routePath.optional(),\n coreExtensionData.routeRef.optional(),\n ]),\n },\n factory(originalFactory, { inputs: _inputs }) {\n // todo(blam): why do we not use the inputs here?\n return originalFactory({\n defaultPath: path[0] === '/' ? path.slice(1) : path,\n routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined,\n loader: async () =>\n compatWrapper(\n route.props.children ? (\n <Routes>\n <Route path=\"*\" element={routeElement}>\n <Route path=\"*\" element={route.props.children} />\n </Route>\n </Routes>\n ) : (\n routeElement\n ),\n ),\n });\n },\n }),\n );\n\n visitRouteChildren({\n children: route.props.children,\n parentExtensionId: pageExtensionId,\n context: {\n pluginId: plugin.getId(),\n extensions,\n getUniqueName,\n discoverPlugin: getPluginExtensions,\n },\n });\n },\n );\n\n return Array.from(pluginExtensions).map(([plugin, extensions]) =>\n createFrontendPlugin({\n id: plugin.getId(),\n extensions: [\n ...extensions,\n ...Array.from(plugin.getApis()).map(factory =>\n ApiBlueprint.make({\n name: factory.api.id,\n params: { factory },\n }),\n ),\n ],\n routes: convertLegacyRouteRefs(plugin.routes ?? {}),\n externalRoutes: convertLegacyRouteRefs(plugin.externalRoutes ?? {}),\n }),\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"collectLegacyRoutes.esm.js","sources":["../src/collectLegacyRoutes.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AnyRouteRefParams,\n BackstagePlugin as LegacyBackstagePlugin,\n RouteRef,\n createPlugin,\n getComponentData,\n} from '@backstage/core-plugin-api';\nimport {\n FrontendPlugin,\n ExtensionDefinition,\n coreExtensionData,\n createExtension,\n createExtensionInput,\n createFrontendPlugin,\n ApiBlueprint,\n PageBlueprint,\n FrontendModule,\n createFrontendModule,\n} from '@backstage/frontend-plugin-api';\nimport React, { Children, ReactNode, isValidElement } from 'react';\nimport { Route, Routes } from 'react-router-dom';\nimport {\n convertLegacyRouteRef,\n convertLegacyRouteRefs,\n} from './convertLegacyRouteRef';\nimport { compatWrapper } from './compatWrapper';\nimport { collectEntityPageContents } from './collectEntityPageContents';\nimport { normalizeRoutePath } from './normalizeRoutePath';\n\n/*\n\n# Legacy interoperability\n\nUse-cases (prioritized):\n 1. Slowly migrate over an existing app to DI, piece by piece\n 2. Use a legacy plugin in a new DI app\n 3. Use DI in an existing legacy app\n\nStarting point: use-case #1\n\nPotential solutions:\n 1. Codemods (we're not considering this for now)\n 2. Legacy apps are migrated bottom-up, i.e. keep legacy root, replace pages with DI\n 3. Legacy apps are migrated top-down i.e. switch out base to DI, legacy adapter allows for usage of existing app structure\n\nChosen path: #3\n\nExisting tasks:\n - Adopters can migrate their existing app gradually (~4)\n - Example-app uses legacy base with DI adapters\n - Create an API that lets you inject DI into existing apps - working assumption is that this is enough\n - Adopters can use legacy plugins in DI through adapters (~8)\n - App-next uses DI base with legacy adapters\n - Create a legacy adapter that is able to take an existing extension tree\n\n*/\n\n// Creates a shim extension whose purpose is to build up the tree (anchored at\n// the root page) of paths/routeRefs so that the app can bind them properly.\nfunction makeRoutingShimExtension(options: {\n name: string;\n parentExtensionId: string;\n routePath?: string;\n routeRef?: RouteRef;\n}) {\n const { name, parentExtensionId, routePath, routeRef } = options;\n return createExtension({\n kind: 'routing-shim',\n name,\n attachTo: { id: parentExtensionId, input: 'childRoutingShims' },\n inputs: {\n childRoutingShims: createExtensionInput([\n coreExtensionData.routePath.optional(),\n coreExtensionData.routeRef.optional(),\n ]),\n },\n output: [\n coreExtensionData.routePath.optional(),\n coreExtensionData.routeRef.optional(),\n ],\n *factory() {\n if (routePath !== undefined) {\n yield coreExtensionData.routePath(routePath);\n }\n\n if (routeRef) {\n yield coreExtensionData.routeRef(convertLegacyRouteRef(routeRef));\n }\n },\n });\n}\n\nexport function visitRouteChildren(options: {\n children: ReactNode;\n parentExtensionId: string;\n context: {\n pluginId: string;\n extensions: ExtensionDefinition[];\n getUniqueName: () => string;\n discoverPlugin: (plugin: LegacyBackstagePlugin) => void;\n };\n}): void {\n const { children, parentExtensionId, context } = options;\n const { pluginId, extensions, getUniqueName, discoverPlugin } = context;\n\n Children.forEach(children, node => {\n if (!isValidElement(node)) {\n return;\n }\n\n const plugin = getComponentData<LegacyBackstagePlugin>(node, 'core.plugin');\n const routeRef = getComponentData<RouteRef<AnyRouteRefParams>>(\n node,\n 'core.mountPoint',\n );\n const routePath: string | undefined = node.props?.path;\n\n if (plugin) {\n // We just mark the plugin as discovered, but don't change the context\n discoverPlugin(plugin);\n }\n\n let nextParentExtensionId = parentExtensionId;\n if (routeRef || routePath) {\n const nextParentExtensionName = getUniqueName();\n nextParentExtensionId = `routing-shim:${pluginId}/${nextParentExtensionName}`;\n extensions.push(\n makeRoutingShimExtension({\n name: nextParentExtensionName,\n parentExtensionId,\n routePath,\n routeRef,\n }),\n );\n }\n\n visitRouteChildren({\n children: node.props.children,\n parentExtensionId: nextParentExtensionId,\n context,\n });\n });\n}\n\n/** @internal */\nexport function collectLegacyRoutes(\n flatRoutesElement: JSX.Element,\n entityPage?: JSX.Element,\n): (FrontendPlugin | FrontendModule)[] {\n const output = new Array<FrontendPlugin | FrontendModule>();\n\n const pluginExtensions = new Map<\n LegacyBackstagePlugin,\n ExtensionDefinition[]\n >();\n\n const getUniqueName = (() => {\n let currentIndex = 1;\n return () => String(currentIndex++);\n })();\n\n // Placeholder plugin for any routes that don't belong to a plugin\n const orphanRoutesPlugin = createPlugin({ id: 'converted-orphan-routes' });\n\n const getPluginExtensions = (plugin: LegacyBackstagePlugin) => {\n let extensions = pluginExtensions.get(plugin);\n if (!extensions) {\n extensions = [];\n pluginExtensions.set(plugin, extensions);\n }\n return extensions;\n };\n\n React.Children.forEach(\n flatRoutesElement.props.children,\n (route: ReactNode) => {\n if (route === null) {\n return;\n }\n // TODO(freben): Handle feature flag and permissions framework wrapper elements\n if (!React.isValidElement(route)) {\n throw new Error(\n `Invalid element inside FlatRoutes, expected Route but found element of type ${typeof route}.`,\n );\n }\n if (route.type !== Route) {\n throw new Error(\n `Invalid element inside FlatRoutes, expected Route but found ${route.type}.`,\n );\n }\n const routeElement = route.props.element;\n const path: string | undefined = route.props.path;\n const plugin =\n getComponentData<LegacyBackstagePlugin>(routeElement, 'core.plugin') ??\n orphanRoutesPlugin;\n const routeRef = getComponentData<RouteRef>(\n routeElement,\n 'core.mountPoint',\n );\n if (path === undefined) {\n throw new Error(\n `Route element inside FlatRoutes had no path prop value given`,\n );\n }\n\n const extensions = getPluginExtensions(plugin);\n const pageExtensionName = extensions.length ? getUniqueName() : undefined;\n const pageExtensionId = `page:${plugin.getId()}${\n pageExtensionName ? `/${pageExtensionName}` : pageExtensionName\n }`;\n\n extensions.push(\n PageBlueprint.makeWithOverrides({\n name: pageExtensionName,\n inputs: {\n childRoutingShims: createExtensionInput([\n coreExtensionData.routePath.optional(),\n coreExtensionData.routeRef.optional(),\n ]),\n },\n factory(originalFactory, { inputs: _inputs }) {\n // todo(blam): why do we not use the inputs here?\n return originalFactory({\n defaultPath: normalizeRoutePath(path),\n routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined,\n loader: async () =>\n compatWrapper(\n route.props.children ? (\n <Routes>\n <Route path=\"*\" element={routeElement}>\n <Route path=\"*\" element={route.props.children} />\n </Route>\n </Routes>\n ) : (\n routeElement\n ),\n ),\n });\n },\n }),\n );\n\n visitRouteChildren({\n children: route.props.children,\n parentExtensionId: pageExtensionId,\n context: {\n pluginId: plugin.getId(),\n extensions,\n getUniqueName,\n discoverPlugin: getPluginExtensions,\n },\n });\n },\n );\n\n if (entityPage) {\n collectEntityPageContents(entityPage, {\n discoverExtension(extension, plugin) {\n if (!plugin || plugin.getId() === 'catalog') {\n getPluginExtensions(orphanRoutesPlugin).push(extension);\n } else {\n getPluginExtensions(plugin).push(extension);\n }\n },\n });\n\n const extensions = new Array<ExtensionDefinition>();\n visitRouteChildren({\n children: entityPage,\n parentExtensionId: `page:catalog/entity`,\n context: {\n pluginId: 'catalog',\n extensions,\n getUniqueName,\n discoverPlugin(plugin) {\n if (plugin.getId() !== 'catalog') {\n getPluginExtensions(plugin);\n }\n },\n },\n });\n\n output.push(\n createFrontendModule({\n pluginId: 'catalog',\n extensions,\n }),\n );\n }\n\n for (const [plugin, extensions] of pluginExtensions) {\n output.push(\n createFrontendPlugin({\n id: plugin.getId(),\n extensions: [\n ...extensions,\n ...Array.from(plugin.getApis()).map(factory =>\n ApiBlueprint.make({\n name: factory.api.id,\n params: { factory },\n }),\n ),\n ],\n routes: convertLegacyRouteRefs(plugin.routes ?? {}),\n externalRoutes: convertLegacyRouteRefs(plugin.externalRoutes ?? {}),\n }),\n );\n }\n\n return output;\n}\n"],"names":[],"mappings":"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"}
@@ -18,9 +18,9 @@ function selectChildren(rootNode, selector, strictError) {
18
18
  return selectChildren(node.props.children, selector);
19
19
  });
20
20
  }
21
- function convertLegacyApp(rootElement) {
21
+ function convertLegacyApp(rootElement, options = {}) {
22
22
  if (getComponentData(rootElement, "core.type") === "FlatRoutes") {
23
- return collectLegacyRoutes(rootElement);
23
+ return collectLegacyRoutes(rootElement, options?.entityPage);
24
24
  }
25
25
  const appRouterEls = selectChildren(
26
26
  rootElement,
@@ -84,7 +84,7 @@ function convertLegacyApp(rootElement) {
84
84
  factory: () => [],
85
85
  disabled: true
86
86
  });
87
- const collectedRoutes = collectLegacyRoutes(routesEl);
87
+ const collectedRoutes = collectLegacyRoutes(routesEl, options?.entityPage);
88
88
  return [
89
89
  ...collectedRoutes,
90
90
  createFrontendModule({
@@ -1 +1 @@
1
- {"version":3,"file":"convertLegacyApp.esm.js","sources":["../src/convertLegacyApp.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 React, {\n Children,\n Fragment,\n ReactElement,\n ReactNode,\n isValidElement,\n} from 'react';\nimport {\n FrontendModule,\n FrontendPlugin,\n coreExtensionData,\n createExtension,\n ExtensionOverrides,\n createExtensionInput,\n createFrontendModule,\n} from '@backstage/frontend-plugin-api';\nimport { getComponentData } from '@backstage/core-plugin-api';\nimport { collectLegacyRoutes } from './collectLegacyRoutes';\nimport { compatWrapper } from './compatWrapper';\n\nfunction selectChildren(\n rootNode: ReactNode,\n selector?: (element: ReactElement<{ children?: ReactNode }>) => boolean,\n strictError?: string,\n): Array<ReactElement<{ children?: ReactNode }>> {\n return Children.toArray(rootNode).flatMap(node => {\n if (!isValidElement<{ children?: ReactNode }>(node)) {\n return [];\n }\n\n if (node.type === Fragment) {\n return selectChildren(node.props.children, selector, strictError);\n }\n\n if (selector === undefined || selector(node)) {\n return [node];\n }\n\n if (strictError) {\n throw new Error(strictError);\n }\n\n return selectChildren(node.props.children, selector, strictError);\n });\n}\n\n/** @public */\nexport function convertLegacyApp(\n rootElement: React.JSX.Element,\n): (FrontendPlugin | FrontendModule | ExtensionOverrides)[] {\n if (getComponentData(rootElement, 'core.type') === 'FlatRoutes') {\n return collectLegacyRoutes(rootElement);\n }\n\n const appRouterEls = selectChildren(\n rootElement,\n el => getComponentData(el, 'core.type') === 'AppRouter',\n );\n if (appRouterEls.length !== 1) {\n throw new Error(\n \"Failed to convert legacy app, AppRouter element could not been found. Make sure it's at the top level of the App element tree\",\n );\n }\n\n const rootEls = selectChildren(\n appRouterEls[0].props.children,\n el =>\n Boolean(el.props.children) &&\n selectChildren(\n el.props.children,\n innerEl => getComponentData(innerEl, 'core.type') === 'FlatRoutes',\n ).length === 1,\n );\n if (rootEls.length !== 1) {\n throw new Error(\n \"Failed to convert legacy app, Root element containing FlatRoutes could not been found. Make sure it's within the AppRouter element of the App element tree\",\n );\n }\n const [rootEl] = rootEls;\n\n const routesEls = selectChildren(\n rootEls[0].props.children,\n el => getComponentData(el, 'core.type') === 'FlatRoutes',\n );\n if (routesEls.length !== 1) {\n throw new Error(\n 'Unexpectedly failed to find FlatRoutes in app element tree',\n );\n }\n const [routesEl] = routesEls;\n\n const CoreLayoutOverride = createExtension({\n name: 'layout',\n attachTo: { id: 'app/root', input: 'children' },\n inputs: {\n content: createExtensionInput([coreExtensionData.reactElement], {\n singleton: true,\n }),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs }) {\n // Clone the root element, this replaces the FlatRoutes declared in the app with out content input\n return [\n coreExtensionData.reactElement(\n compatWrapper(\n React.cloneElement(\n rootEl,\n undefined,\n inputs.content.get(coreExtensionData.reactElement),\n ),\n ),\n ),\n ];\n },\n });\n const CoreNavOverride = createExtension({\n name: 'nav',\n attachTo: { id: 'app/layout', input: 'nav' },\n output: [],\n factory: () => [],\n disabled: true,\n });\n\n const collectedRoutes = collectLegacyRoutes(routesEl);\n\n return [\n ...collectedRoutes,\n createFrontendModule({\n pluginId: 'app',\n extensions: [CoreLayoutOverride, CoreNavOverride],\n }),\n ];\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"convertLegacyApp.esm.js","sources":["../src/convertLegacyApp.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 React, {\n Children,\n Fragment,\n ReactElement,\n ReactNode,\n isValidElement,\n} from 'react';\nimport {\n FrontendModule,\n FrontendPlugin,\n coreExtensionData,\n createExtension,\n createExtensionInput,\n createFrontendModule,\n} from '@backstage/frontend-plugin-api';\nimport { getComponentData } from '@backstage/core-plugin-api';\nimport { collectLegacyRoutes } from './collectLegacyRoutes';\nimport { compatWrapper } from './compatWrapper';\n\nfunction selectChildren(\n rootNode: ReactNode,\n selector?: (element: ReactElement<{ children?: ReactNode }>) => boolean,\n strictError?: string,\n): Array<ReactElement<{ children?: ReactNode }>> {\n return Children.toArray(rootNode).flatMap(node => {\n if (!isValidElement<{ children?: ReactNode }>(node)) {\n return [];\n }\n\n if (node.type === Fragment) {\n return selectChildren(node.props.children, selector, strictError);\n }\n\n if (selector === undefined || selector(node)) {\n return [node];\n }\n\n if (strictError) {\n throw new Error(strictError);\n }\n\n return selectChildren(node.props.children, selector, strictError);\n });\n}\n\n/** @public */\nexport interface ConvertLegacyAppOptions {\n /**\n * By providing an entity page element here it will be split up and converted\n * into individual extensions for the catalog plugin in the new frontend\n * system.\n *\n * In order to use this option the entity page and other catalog extensions\n * must be removed from the legacy app, and the catalog plugin for the new\n * frontend system must be installed instead.\n *\n * In order for this conversion to work the entity page must be a plain React\n * element tree without any component wrapping anywhere between the root\n * element and the `EntityLayout.Route` elements.\n *\n * When enabling this conversion you are likely to encounter duplicate entity\n * page content provided both via the old structure and the new plugins. Any\n * duplicate content needs to be removed from the old structure.\n */\n entityPage?: React.JSX.Element;\n}\n\n/** @public */\nexport function convertLegacyApp(\n rootElement: React.JSX.Element,\n options: ConvertLegacyAppOptions = {},\n): (FrontendPlugin | FrontendModule)[] {\n if (getComponentData(rootElement, 'core.type') === 'FlatRoutes') {\n return collectLegacyRoutes(rootElement, options?.entityPage);\n }\n\n const appRouterEls = selectChildren(\n rootElement,\n el => getComponentData(el, 'core.type') === 'AppRouter',\n );\n if (appRouterEls.length !== 1) {\n throw new Error(\n \"Failed to convert legacy app, AppRouter element could not been found. Make sure it's at the top level of the App element tree\",\n );\n }\n\n const rootEls = selectChildren(\n appRouterEls[0].props.children,\n el =>\n Boolean(el.props.children) &&\n selectChildren(\n el.props.children,\n innerEl => getComponentData(innerEl, 'core.type') === 'FlatRoutes',\n ).length === 1,\n );\n if (rootEls.length !== 1) {\n throw new Error(\n \"Failed to convert legacy app, Root element containing FlatRoutes could not been found. Make sure it's within the AppRouter element of the App element tree\",\n );\n }\n const [rootEl] = rootEls;\n\n const routesEls = selectChildren(\n rootEls[0].props.children,\n el => getComponentData(el, 'core.type') === 'FlatRoutes',\n );\n if (routesEls.length !== 1) {\n throw new Error(\n 'Unexpectedly failed to find FlatRoutes in app element tree',\n );\n }\n const [routesEl] = routesEls;\n\n const CoreLayoutOverride = createExtension({\n name: 'layout',\n attachTo: { id: 'app/root', input: 'children' },\n inputs: {\n content: createExtensionInput([coreExtensionData.reactElement], {\n singleton: true,\n }),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs }) {\n // Clone the root element, this replaces the FlatRoutes declared in the app with out content input\n return [\n coreExtensionData.reactElement(\n compatWrapper(\n React.cloneElement(\n rootEl,\n undefined,\n inputs.content.get(coreExtensionData.reactElement),\n ),\n ),\n ),\n ];\n },\n });\n const CoreNavOverride = createExtension({\n name: 'nav',\n attachTo: { id: 'app/layout', input: 'nav' },\n output: [],\n factory: () => [],\n disabled: true,\n });\n\n const collectedRoutes = collectLegacyRoutes(routesEl, options?.entityPage);\n\n return [\n ...collectedRoutes,\n createFrontendModule({\n pluginId: 'app',\n extensions: [CoreLayoutOverride, CoreNavOverride],\n }),\n ];\n}\n"],"names":[],"mappings":";;;;;;AAmCA,SAAS,cAAA,CACP,QACA,EAAA,QAAA,EACA,WAC+C,EAAA;AAC/C,EAAA,OAAO,QAAS,CAAA,OAAA,CAAQ,QAAQ,CAAA,CAAE,QAAQ,CAAQ,IAAA,KAAA;AAChD,IAAI,IAAA,CAAC,cAAyC,CAAA,IAAI,CAAG,EAAA;AACnD,MAAA,OAAO,EAAC;AAAA;AAGV,IAAI,IAAA,IAAA,CAAK,SAAS,QAAU,EAAA;AAC1B,MAAA,OAAO,cAAe,CAAA,IAAA,CAAK,KAAM,CAAA,QAAA,EAAU,QAAqB,CAAA;AAAA;AAGlE,IAAA,IAAI,QAAa,KAAA,KAAA,CAAA,IAAa,QAAS,CAAA,IAAI,CAAG,EAAA;AAC5C,MAAA,OAAO,CAAC,IAAI,CAAA;AAAA;AAOd,IAAA,OAAO,cAAe,CAAA,IAAA,CAAK,KAAM,CAAA,QAAA,EAAU,QAAqB,CAAA;AAAA,GACjE,CAAA;AACH;AAyBO,SAAS,gBACd,CAAA,WAAA,EACA,OAAmC,GAAA,EACE,EAAA;AACrC,EAAA,IAAI,gBAAiB,CAAA,WAAA,EAAa,WAAW,CAAA,KAAM,YAAc,EAAA;AAC/D,IAAO,OAAA,mBAAA,CAAoB,WAAa,EAAA,OAAA,EAAS,UAAU,CAAA;AAAA;AAG7D,EAAA,MAAM,YAAe,GAAA,cAAA;AAAA,IACnB,WAAA;AAAA,IACA,CAAM,EAAA,KAAA,gBAAA,CAAiB,EAAI,EAAA,WAAW,CAAM,KAAA;AAAA,GAC9C;AACA,EAAI,IAAA,YAAA,CAAa,WAAW,CAAG,EAAA;AAC7B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA;AAGF,EAAA,MAAM,OAAU,GAAA,cAAA;AAAA,IACd,YAAA,CAAa,CAAC,CAAA,CAAE,KAAM,CAAA,QAAA;AAAA,IACtB,CACE,EAAA,KAAA,OAAA,CAAQ,EAAG,CAAA,KAAA,CAAM,QAAQ,CACzB,IAAA,cAAA;AAAA,MACE,GAAG,KAAM,CAAA,QAAA;AAAA,MACT,CAAW,OAAA,KAAA,gBAAA,CAAiB,OAAS,EAAA,WAAW,CAAM,KAAA;AAAA,MACtD,MAAW,KAAA;AAAA,GACjB;AACA,EAAI,IAAA,OAAA,CAAQ,WAAW,CAAG,EAAA;AACxB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA;AAEF,EAAM,MAAA,CAAC,MAAM,CAAI,GAAA,OAAA;AAEjB,EAAA,MAAM,SAAY,GAAA,cAAA;AAAA,IAChB,OAAA,CAAQ,CAAC,CAAA,CAAE,KAAM,CAAA,QAAA;AAAA,IACjB,CAAM,EAAA,KAAA,gBAAA,CAAiB,EAAI,EAAA,WAAW,CAAM,KAAA;AAAA,GAC9C;AACA,EAAI,IAAA,SAAA,CAAU,WAAW,CAAG,EAAA;AAC1B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA;AAEF,EAAM,MAAA,CAAC,QAAQ,CAAI,GAAA,SAAA;AAEnB,EAAA,MAAM,qBAAqB,eAAgB,CAAA;AAAA,IACzC,IAAM,EAAA,QAAA;AAAA,IACN,QAAU,EAAA,EAAE,EAAI,EAAA,UAAA,EAAY,OAAO,UAAW,EAAA;AAAA,IAC9C,MAAQ,EAAA;AAAA,MACN,OAAS,EAAA,oBAAA,CAAqB,CAAC,iBAAA,CAAkB,YAAY,CAAG,EAAA;AAAA,QAC9D,SAAW,EAAA;AAAA,OACZ;AAAA,KACH;AAAA,IACA,MAAA,EAAQ,CAAC,iBAAA,CAAkB,YAAY,CAAA;AAAA,IACvC,OAAA,CAAQ,EAAE,MAAA,EAAU,EAAA;AAElB,MAAO,OAAA;AAAA,QACL,iBAAkB,CAAA,YAAA;AAAA,UAChB,aAAA;AAAA,YACE,KAAM,CAAA,YAAA;AAAA,cACJ,MAAA;AAAA,cACA,KAAA,CAAA;AAAA,cACA,MAAO,CAAA,OAAA,CAAQ,GAAI,CAAA,iBAAA,CAAkB,YAAY;AAAA;AACnD;AACF;AACF,OACF;AAAA;AACF,GACD,CAAA;AACD,EAAA,MAAM,kBAAkB,eAAgB,CAAA;AAAA,IACtC,IAAM,EAAA,KAAA;AAAA,IACN,QAAU,EAAA,EAAE,EAAI,EAAA,YAAA,EAAc,OAAO,KAAM,EAAA;AAAA,IAC3C,QAAQ,EAAC;AAAA,IACT,OAAA,EAAS,MAAM,EAAC;AAAA,IAChB,QAAU,EAAA;AAAA,GACX,CAAA;AAED,EAAA,MAAM,eAAkB,GAAA,mBAAA,CAAoB,QAAU,EAAA,OAAA,EAAS,UAAU,CAAA;AAEzE,EAAO,OAAA;AAAA,IACL,GAAG,eAAA;AAAA,IACH,oBAAqB,CAAA;AAAA,MACnB,QAAU,EAAA,KAAA;AAAA,MACV,UAAA,EAAY,CAAC,kBAAA,EAAoB,eAAe;AAAA,KACjD;AAAA,GACH;AACF;;;;"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React, { ReactNode, ComponentType } from 'react';
2
2
  import { AnalyticsApi, AnalyticsEvent, AnyApiFactory, IconComponent, BackstagePlugin, AppComponents, AppTheme, FeatureFlag, RouteRef, SubRouteRef, ExternalRouteRef, AnyRouteRefParams } from '@backstage/core-plugin-api';
3
- import { AnalyticsApi as AnalyticsApi$1, AnalyticsEvent as AnalyticsEvent$1, FrontendPlugin, FrontendModule, ExtensionOverrides, ExtensionDefinition, RouteRef as RouteRef$1, SubRouteRef as SubRouteRef$1, ExternalRouteRef as ExternalRouteRef$1 } from '@backstage/frontend-plugin-api';
3
+ import { AnalyticsApi as AnalyticsApi$1, AnalyticsEvent as AnalyticsEvent$1, FrontendPlugin, FrontendModule, ExtensionDefinition, RouteRef as RouteRef$1, SubRouteRef as SubRouteRef$1, ExternalRouteRef as ExternalRouteRef$1 } from '@backstage/frontend-plugin-api';
4
4
 
5
5
  /**
6
6
  * Wraps a React element in a bidirectional compatibility provider, allow APIs
@@ -64,7 +64,28 @@ declare class NoOpAnalyticsApi implements AnalyticsApi, AnalyticsApi$1 {
64
64
  }
65
65
 
66
66
  /** @public */
67
- declare function convertLegacyApp(rootElement: React.JSX.Element): (FrontendPlugin | FrontendModule | ExtensionOverrides)[];
67
+ interface ConvertLegacyAppOptions {
68
+ /**
69
+ * By providing an entity page element here it will be split up and converted
70
+ * into individual extensions for the catalog plugin in the new frontend
71
+ * system.
72
+ *
73
+ * In order to use this option the entity page and other catalog extensions
74
+ * must be removed from the legacy app, and the catalog plugin for the new
75
+ * frontend system must be installed instead.
76
+ *
77
+ * In order for this conversion to work the entity page must be a plain React
78
+ * element tree without any component wrapping anywhere between the root
79
+ * element and the `EntityLayout.Route` elements.
80
+ *
81
+ * When enabling this conversion you are likely to encounter duplicate entity
82
+ * page content provided both via the old structure and the new plugins. Any
83
+ * duplicate content needs to be removed from the old structure.
84
+ */
85
+ entityPage?: React.JSX.Element;
86
+ }
87
+ /** @public */
88
+ declare function convertLegacyApp(rootElement: React.JSX.Element, options?: ConvertLegacyAppOptions): (FrontendPlugin | FrontendModule)[];
68
89
 
69
90
  /**
70
91
  * @public
@@ -163,4 +184,4 @@ declare function convertLegacyRouteRef<TParams extends AnyRouteRefParams>(ref: S
163
184
  */
164
185
  declare function convertLegacyRouteRef<TParams extends AnyRouteRefParams>(ref: ExternalRouteRef$1<TParams>): ExternalRouteRef<TParams, true>;
165
186
 
166
- export { MultipleAnalyticsApi, NoOpAnalyticsApi, type ToNewRouteRef, compatWrapper, convertLegacyApp, convertLegacyAppOptions, convertLegacyPageExtension, convertLegacyPlugin, convertLegacyRouteRef, convertLegacyRouteRefs };
187
+ export { type ConvertLegacyAppOptions, MultipleAnalyticsApi, NoOpAnalyticsApi, type ToNewRouteRef, compatWrapper, convertLegacyApp, convertLegacyAppOptions, convertLegacyPageExtension, convertLegacyPlugin, convertLegacyRouteRef, convertLegacyRouteRefs };
@@ -0,0 +1,16 @@
1
+ function normalizeRoutePath(path) {
2
+ let normalized = path;
3
+ while (normalized.endsWith("/") || normalized.endsWith("*")) {
4
+ normalized = normalized.slice(0, -1);
5
+ }
6
+ while (normalized.startsWith("/")) {
7
+ normalized = normalized.slice(1);
8
+ }
9
+ if (!normalized) {
10
+ return "/";
11
+ }
12
+ return `/${normalized}`;
13
+ }
14
+
15
+ export { normalizeRoutePath };
16
+ //# sourceMappingURL=normalizeRoutePath.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizeRoutePath.esm.js","sources":["../src/normalizeRoutePath.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\n/**\n * Normalizes the path to make sure it always starts with a single '/' and do not end with '/' or '*' unless empty\n */\nexport function normalizeRoutePath(path: string) {\n let normalized = path;\n while (normalized.endsWith('/') || normalized.endsWith('*')) {\n normalized = normalized.slice(0, -1);\n }\n while (normalized.startsWith('/')) {\n normalized = normalized.slice(1);\n }\n if (!normalized) {\n return '/';\n }\n return `/${normalized}`;\n}\n"],"names":[],"mappings":"AAmBO,SAAS,mBAAmB,IAAc,EAAA;AAC/C,EAAA,IAAI,UAAa,GAAA,IAAA;AACjB,EAAA,OAAO,WAAW,QAAS,CAAA,GAAG,KAAK,UAAW,CAAA,QAAA,CAAS,GAAG,CAAG,EAAA;AAC3D,IAAa,UAAA,GAAA,UAAA,CAAW,KAAM,CAAA,CAAA,EAAG,CAAE,CAAA,CAAA;AAAA;AAErC,EAAO,OAAA,UAAA,CAAW,UAAW,CAAA,GAAG,CAAG,EAAA;AACjC,IAAa,UAAA,GAAA,UAAA,CAAW,MAAM,CAAC,CAAA;AAAA;AAEjC,EAAA,IAAI,CAAC,UAAY,EAAA;AACf,IAAO,OAAA,GAAA;AAAA;AAET,EAAA,OAAO,IAAI,UAAU,CAAA,CAAA;AACvB;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/core-compat-api",
3
- "version": "0.3.7-next.1",
3
+ "version": "0.4.0",
4
4
  "backstage": {
5
5
  "role": "web-library"
6
6
  },
@@ -31,21 +31,22 @@
31
31
  "test": "backstage-cli package test"
32
32
  },
33
33
  "dependencies": {
34
- "@backstage/core-plugin-api": "1.10.4",
35
- "@backstage/frontend-plugin-api": "0.9.6-next.1",
36
- "@backstage/version-bridge": "1.0.11",
34
+ "@backstage/core-plugin-api": "^1.10.5",
35
+ "@backstage/frontend-plugin-api": "^0.10.0",
36
+ "@backstage/plugin-catalog-react": "^1.16.0",
37
+ "@backstage/version-bridge": "^1.0.11",
37
38
  "lodash": "^4.17.21"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@backstage-community/plugin-puppetdb": "^0.1.18",
41
42
  "@backstage-community/plugin-stackstorm": "^0.1.16",
42
- "@backstage/cli": "0.30.1-next.0",
43
- "@backstage/core-app-api": "1.15.5",
44
- "@backstage/frontend-app-api": "0.10.6-next.1",
45
- "@backstage/frontend-test-utils": "0.2.7-next.1",
46
- "@backstage/plugin-catalog": "1.28.0-next.1",
47
- "@backstage/test-utils": "1.7.5",
48
- "@backstage/types": "1.2.1",
43
+ "@backstage/cli": "^0.31.0",
44
+ "@backstage/core-app-api": "^1.16.0",
45
+ "@backstage/frontend-app-api": "^0.11.0",
46
+ "@backstage/frontend-test-utils": "^0.3.0",
47
+ "@backstage/plugin-catalog": "^1.28.0",
48
+ "@backstage/test-utils": "^1.7.6",
49
+ "@backstage/types": "^1.2.1",
49
50
  "@oriflame/backstage-plugin-score-card": "^0.9.0",
50
51
  "@testing-library/jest-dom": "^6.0.0",
51
52
  "@testing-library/react": "^16.0.0",
@@ -66,12 +67,5 @@
66
67
  "optional": true
67
68
  }
68
69
  },
69
- "typesVersions": {
70
- "*": {
71
- "index": [
72
- "dist/index.d.ts"
73
- ]
74
- }
75
- },
76
70
  "module": "./dist/index.esm.js"
77
71
  }