diff --git a/package.json b/package.json
index d4ebc22bc1..88238a1a21 100644
--- a/package.json
+++ b/package.json
@@ -185,5 +185,8 @@
}
},
"tsConfig": "./tsconfig.json"
+ },
+ "volta": {
+ "node": "22.19.0"
}
}
diff --git a/packages/app-next/package.json b/packages/app-next/package.json
index 44602b2438..0a39471e34 100644
--- a/packages/app-next/package.json
+++ b/packages/app-next/package.json
@@ -56,6 +56,7 @@
"@backstage/plugin-catalog-import": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-catalog-unprocessed-entities": "workspace:^",
+ "@backstage/plugin-devtools": "workspace:^",
"@backstage/plugin-home": "workspace:^",
"@backstage/plugin-kubernetes": "workspace:^",
"@backstage/plugin-kubernetes-cluster": "workspace:^",
diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx
index 6c567f3fe1..92d1f06d6a 100644
--- a/packages/app-next/src/App.tsx
+++ b/packages/app-next/src/App.tsx
@@ -45,6 +45,8 @@ import { convertLegacyPageExtension } from '@backstage/core-compat-api';
import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha';
import { pluginInfoResolver } from './pluginInfoResolver';
import { appModuleNav } from './modules/appModuleNav';
+import devtoolsPlugin from '@backstage/plugin-devtools/alpha';
+import { unprocessedEntitiesDevToolsRoute } from '@backstage/plugin-catalog-unprocessed-entities/alpha';
/*
@@ -113,7 +115,7 @@ const customHomePageModule = createFrontendModule({
const notFoundErrorPageModule = createFrontendModule({
pluginId: 'app',
- extensions: [notFoundErrorPage],
+ extensions: [notFoundErrorPage, unprocessedEntitiesDevToolsRoute],
});
const collectedLegacyPlugins = convertLegacyAppRoot(
@@ -133,6 +135,7 @@ const app = createApp({
notFoundErrorPageModule,
appModuleNav,
customHomePageModule,
+ devtoolsPlugin,
...collectedLegacyPlugins,
],
advanced: {
diff --git a/packages/backend/package.json b/packages/backend/package.json
index a60b6d3940..0d03b12d19 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -71,5 +71,8 @@
},
"devDependencies": {
"@backstage/cli": "workspace:^"
+ },
+ "volta": {
+ "node": "22.19.0"
}
}
diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json
index 004eb96208..c48f290d25 100644
--- a/plugins/catalog-unprocessed-entities/package.json
+++ b/plugins/catalog-unprocessed-entities/package.json
@@ -56,6 +56,7 @@
"@backstage/core-plugin-api": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
+ "@backstage/plugin-devtools": "workspace:^",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.60",
diff --git a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts
new file mode 100644
index 0000000000..c90a6b8270
--- /dev/null
+++ b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools/alpha';
+
+/**
+ * DevTools route extension for catalog unprocessed entities
+ * This demonstrates how to create extensions at the app level using the new DevTools extension system
+ */
+export const unprocessedEntitiesDevToolsRoute = DevToolsRouteBlueprint.make({
+ params: {
+ path: 'unprocessed-entities',
+ title: 'Unprocessed Entities',
+ loader: () =>
+ import('../components/UnprocessedEntities').then(m => ({
+ default: m.UnprocessedEntitiesContent,
+ })),
+ },
+});
diff --git a/plugins/catalog-unprocessed-entities/src/alpha/index.ts b/plugins/catalog-unprocessed-entities/src/alpha/index.ts
index 2f137f09ee..5738bb8a59 100644
--- a/plugins/catalog-unprocessed-entities/src/alpha/index.ts
+++ b/plugins/catalog-unprocessed-entities/src/alpha/index.ts
@@ -15,3 +15,4 @@
*/
export { default } from './plugin';
+export { unprocessedEntitiesDevToolsRoute } from './devToolsRoute';
diff --git a/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx b/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx
new file mode 100644
index 0000000000..fec93854ae
--- /dev/null
+++ b/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createExtensionBlueprint } from '@backstage/frontend-plugin-api';
+import { devToolsRouteDataRef } from './devToolsRouteDataRef';
+import { lazy, Suspense, JSX } from 'react';
+
+/**
+ * Parameters for creating a DevTools route extension
+ * @alpha
+ */
+export interface DevToolsRouteBlueprintParams {
+ path: string;
+ title: string;
+ loader: () => Promise<{ default: () => JSX.Element }> | (() => JSX.Element);
+}
+
+/**
+ * Extension blueprint for creating DevTools routes
+ *
+ * @example
+ * ```tsx
+ * const myDevToolsRoute = DevToolsRouteBlueprint.make({
+ * params: {
+ * path: 'my-feature',
+ * title: 'My Feature',
+ * loader: () => import('./MyContent').then(m => ({ default: m.MyContent }))
+ * }
+ * });
+ * ```
+ *
+ * @alpha
+ */
+export const DevToolsRouteBlueprint = createExtensionBlueprint({
+ kind: 'devtools-route',
+ attachTo: { id: 'page:devtools', input: 'routes' },
+ output: [devToolsRouteDataRef],
+ factory(params: DevToolsRouteBlueprintParams) {
+ // Handle both sync and async component loading
+ const Component = typeof params.loader === 'function'
+ ? (() => {
+ const result = params.loader();
+ if (result instanceof Promise) {
+ // Create a lazy component for async loading
+ return lazy(() => result);
+ }
+ // Direct component function
+ return result;
+
+ })()
+ : params.loader;
+
+ const children = typeof Component === 'function'
+ ? Loading...}>
+ : Component;
+
+ return [
+ devToolsRouteDataRef({
+ path: params.path,
+ title: params.title,
+ children,
+ }),
+ ];
+ },
+ dataRefs: {
+ route: devToolsRouteDataRef,
+ },
+});
\ No newline at end of file
diff --git a/plugins/devtools/src/alpha/devToolsRouteDataRef.ts b/plugins/devtools/src/alpha/devToolsRouteDataRef.ts
new file mode 100644
index 0000000000..f4533f13e5
--- /dev/null
+++ b/plugins/devtools/src/alpha/devToolsRouteDataRef.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createExtensionDataRef } from '@backstage/frontend-plugin-api';
+import { JSX } from 'react';
+
+/**
+ * Represents a DevTools route that can be contributed by extensions
+ * @alpha
+ */
+export interface DevToolsRouteData {
+ path: string;
+ title: string;
+ children: JSX.Element;
+}
+
+/**
+ * Extension data reference for DevTools routes
+ * @alpha
+ */
+export const devToolsRouteDataRef = createExtensionDataRef()
+ .with({
+ id: 'devtools.route',
+ });
\ No newline at end of file
diff --git a/plugins/devtools/src/alpha/index.ts b/plugins/devtools/src/alpha/index.ts
index 2f137f09ee..d326cac1be 100644
--- a/plugins/devtools/src/alpha/index.ts
+++ b/plugins/devtools/src/alpha/index.ts
@@ -15,3 +15,7 @@
*/
export { default } from './plugin';
+export { DevToolsRouteBlueprint } from './devToolsRouteBlueprint';
+export { devToolsRouteDataRef } from './devToolsRouteDataRef';
+export type { DevToolsRouteData } from './devToolsRouteDataRef';
+export type { DevToolsRouteBlueprintParams } from './devToolsRouteBlueprint';
diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx
index 0f6d9ec4a1..02dca0141a 100644
--- a/plugins/devtools/src/alpha/plugin.tsx
+++ b/plugins/devtools/src/alpha/plugin.tsx
@@ -21,6 +21,7 @@ import {
ApiBlueprint,
PageBlueprint,
NavItemBlueprint,
+ createExtensionInput,
} from '@backstage/frontend-plugin-api';
import { devToolsApiRef, DevToolsClient } from '../api';
@@ -30,6 +31,7 @@ import {
} from '@backstage/core-compat-api';
import BuildIcon from '@material-ui/icons/Build';
import { rootRouteRef } from '../routes';
+import { devToolsRouteDataRef } from './devToolsRouteDataRef';
/** @alpha */
export const devToolsApi = ApiBlueprint.make({
@@ -46,14 +48,21 @@ export const devToolsApi = ApiBlueprint.make({
});
/** @alpha */
-export const devToolsPage = PageBlueprint.make({
- params: {
- path: '/devtools',
- routeRef: convertLegacyRouteRef(rootRouteRef),
- loader: () =>
- import('../components/DevToolsPage').then(m =>
- compatWrapper(),
- ),
+export const devToolsPage = PageBlueprint.makeWithOverrides({
+ inputs: {
+ routes: createExtensionInput([devToolsRouteDataRef], {
+ optional: true,
+ }),
+ },
+ factory(originalFactory, { inputs }) {
+ return originalFactory({
+ path: '/devtools',
+ routeRef: convertLegacyRouteRef(rootRouteRef),
+ loader: () =>
+ import('../components/DevToolsPage').then(m =>
+ compatWrapper( route.get(devToolsRouteDataRef))} />),
+ ),
+ });
},
});
diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
index b4e5d3db5b..6375e562f4 100644
--- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
+++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
@@ -23,9 +23,14 @@ import { ConfigContent } from '../Content/ConfigContent';
import { DevToolsLayout } from '../DevToolsLayout';
import { InfoContent } from '../Content/InfoContent';
import { RequirePermission } from '@backstage/plugin-permission-react';
+import { DevToolsRouteData } from '../../alpha/devToolsRouteDataRef';
+
+export interface DefaultDevToolsPageProps {
+ extensionRoutes?: DevToolsRouteData[];
+}
/** @public */
-export const DefaultDevToolsPage = () => (
+export const DefaultDevToolsPage = ({ extensionRoutes }: DefaultDevToolsPageProps) => (
@@ -37,5 +42,14 @@ export const DefaultDevToolsPage = () => (
+ {extensionRoutes?.map((route, index) => (
+
+ {route.children}
+
+ ))}
);
diff --git a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx
index 07a549f8eb..6c351bbf96 100644
--- a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx
+++ b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx
@@ -16,9 +16,14 @@
import { useOutlet } from 'react-router-dom';
import { DefaultDevToolsPage } from '../DefaultDevToolsPage';
+import { DevToolsRouteData } from '../../alpha/devToolsRouteDataRef';
-export const DevToolsPage = () => {
+export interface DevToolsPageProps {
+ extensionRoutes?: DevToolsRouteData[];
+}
+
+export const DevToolsPage = ({ extensionRoutes }: DevToolsPageProps) => {
const outlet = useOutlet();
- return <>{outlet || }>;
+ return <>{outlet || }>;
};
diff --git a/yarn.lock b/yarn.lock
index 853749b99e..b90990c0b6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4938,6 +4938,7 @@ __metadata:
"@backstage/dev-utils": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
+ "@backstage/plugin-devtools": "workspace:^"
"@material-ui/core": "npm:^4.9.13"
"@material-ui/icons": "npm:^4.9.1"
"@material-ui/lab": "npm:^4.0.0-alpha.60"
@@ -29922,6 +29923,7 @@ __metadata:
"@backstage/plugin-catalog-import": "workspace:^"
"@backstage/plugin-catalog-react": "workspace:^"
"@backstage/plugin-catalog-unprocessed-entities": "workspace:^"
+ "@backstage/plugin-devtools": "workspace:^"
"@backstage/plugin-home": "workspace:^"
"@backstage/plugin-kubernetes": "workspace:^"
"@backstage/plugin-kubernetes-cluster": "workspace:^"