diff --git a/.changeset/thin-experts-sing.md b/.changeset/thin-experts-sing.md
new file mode 100644
index 0000000000..55aa8eac90
--- /dev/null
+++ b/.changeset/thin-experts-sing.md
@@ -0,0 +1,10 @@
+---
+'@backstage/core-compat-api': patch
+---
+
+The `convertLegacyApp` has received the following changes:
+
+- `null` routes will now be ignored.
+- Converted routes no longer need to belong to a plugin, falling back to a `converted-orphan-routes` plugin instead.
+- The generate layout override extension is now properly attached to the `app/root` extension.
+- Converted root elements are now automatically wrapped with `compatWrapper`.
diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx
index a28bda6dea..ce0b055c00 100644
--- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx
+++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx
@@ -48,8 +48,10 @@ describe('collectLegacyRoutes', () => {
} />
} />
+ } />
} />
} />
+ } />
,
);
@@ -96,6 +98,23 @@ describe('collectLegacyRoutes', () => {
},
],
},
+ {
+ id: 'converted-orphan-routes',
+ extensions: [
+ {
+ id: 'page:converted-orphan-routes',
+ attachTo: { id: 'app/routes', input: 'routes' },
+ disabled: false,
+ defaultConfig: {},
+ },
+ {
+ id: 'page:converted-orphan-routes/2',
+ attachTo: { id: 'app/routes', input: 'routes' },
+ disabled: false,
+ defaultConfig: {},
+ },
+ ],
+ },
{
id: 'puppetDb',
extensions: [
@@ -378,8 +397,6 @@ describe('collectLegacyRoutes', () => {
} />
,
),
- ).toThrow(
- /Route with path undefined has en element that can not be converted/,
- );
+ ).toThrow(/Route element inside FlatRoutes had no path prop value given/);
});
});
diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx
index f3d94dc4b9..b0a55f9ddd 100644
--- a/packages/core-compat-api/src/collectLegacyRoutes.tsx
+++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx
@@ -18,6 +18,7 @@ import {
AnyRouteRefParams,
BackstagePlugin as LegacyBackstagePlugin,
RouteRef,
+ createPlugin,
getComponentData,
} from '@backstage/core-plugin-api';
import {
@@ -167,6 +168,9 @@ export function collectLegacyRoutes(
return () => String(currentIndex++);
})();
+ // Placeholder plugin for any routes that don't belong to a plugin
+ const orphanRoutesPlugin = createPlugin({ id: 'converted-orphan-routes' });
+
const getPluginExtensions = (plugin: LegacyBackstagePlugin) => {
let extensions = pluginExtensions.get(plugin);
if (!extensions) {
@@ -179,6 +183,9 @@ export function collectLegacyRoutes(
React.Children.forEach(
flatRoutesElement.props.children,
(route: ReactNode) => {
+ if (route === null) {
+ return;
+ }
// TODO(freben): Handle feature flag and permissions framework wrapper elements
if (!React.isValidElement(route)) {
throw new Error(
@@ -192,20 +199,13 @@ export function collectLegacyRoutes(
}
const routeElement = route.props.element;
const path: string | undefined = route.props.path;
- const plugin = getComponentData(
- routeElement,
- 'core.plugin',
- );
+ const plugin =
+ getComponentData(routeElement, 'core.plugin') ??
+ orphanRoutesPlugin;
const routeRef = getComponentData(
routeElement,
'core.mountPoint',
);
- if (!plugin) {
- throw new Error(
- // TODO(vinzscam): add See for more info
- `Route with path ${path} has en element that can not be converted as it does not belong to a plugin. Make sure that the top-level React element of the element prop is an extension from a Backstage plugin, or remove the Route completely.`,
- );
- }
if (path === undefined) {
throw new Error(
`Route element inside FlatRoutes had no path prop value given`,
diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx
index 224ab46221..7153fcb4be 100644
--- a/packages/core-compat-api/src/convertLegacyApp.test.tsx
+++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx
@@ -114,7 +114,7 @@ describe('convertLegacyApp', () => {
extensions: [
{
id: 'app/layout',
- attachTo: { id: 'app', input: 'root' },
+ attachTo: { id: 'app/root', input: 'children' },
disabled: false,
},
{
diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts
index 3f35c78408..f098d090ed 100644
--- a/packages/core-compat-api/src/convertLegacyApp.ts
+++ b/packages/core-compat-api/src/convertLegacyApp.ts
@@ -32,6 +32,7 @@ import {
} from '@backstage/frontend-plugin-api';
import { getComponentData } from '@backstage/core-plugin-api';
import { collectLegacyRoutes } from './collectLegacyRoutes';
+import { compatWrapper } from './compatWrapper';
function selectChildren(
rootNode: ReactNode,
@@ -106,7 +107,7 @@ export function convertLegacyApp(
const CoreLayoutOverride = createExtension({
name: 'layout',
- attachTo: { id: 'app', input: 'root' },
+ attachTo: { id: 'app/root', input: 'children' },
inputs: {
content: createExtensionInput([coreExtensionData.reactElement], {
singleton: true,
@@ -117,10 +118,12 @@ export function convertLegacyApp(
// Clone the root element, this replaces the FlatRoutes declared in the app with out content input
return [
coreExtensionData.reactElement(
- React.cloneElement(
- rootEl,
- undefined,
- inputs.content.get(coreExtensionData.reactElement),
+ compatWrapper(
+ React.cloneElement(
+ rootEl,
+ undefined,
+ inputs.content.get(coreExtensionData.reactElement),
+ ),
),
),
];