repo: promote app-next to main example app

This renames packages to make the new frontend system the default:

- packages/app → packages/app-legacy (example-app-legacy)
- packages/app-next → packages/app (example-app)
- packages/app-next-example-plugin → packages/app-example-plugin

Updated all related configuration, scripts, and documentation.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Patrik Oldsberg
2026-02-05 23:12:06 +01:00
parent 9848734ce6
commit be7ebadb21
93 changed files with 15461 additions and 15241 deletions
+3 -3
View File
@@ -2,10 +2,10 @@
"mode": "pre",
"tag": "next",
"initialVersions": {
"example-app": "0.2.117",
"example-app-legacy": "0.2.117",
"@backstage/app-defaults": "1.7.4",
"example-app-next": "0.0.31",
"app-next-example-plugin": "0.0.31",
"example-app": "0.0.31",
"app-example-plugin": "0.0.31",
"example-backend": "0.0.46",
"@backstage/backend-app-api": "1.4.1",
"@backstage/backend-defaults": "0.15.0",
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': patch
---
Updated package-docs exclude list to reflect renamed example app packages.
+2 -1
View File
@@ -5,7 +5,8 @@ Backstage is an open platform for building developer portals. This is a TypeScri
- `/packages`: Core framework packages (prefixed `@backstage/`)
- `/plugins`: Plugin packages (prefixed `@backstage/plugin-*`)
- `/packages/app` and `/packages/backend`: Example app for local development
- `/packages/app-next`: Example app using the new frontend system (NFS)
- `/packages/app`: Main example app using the new frontend system
- `/packages/app-legacy`: Example app using the old frontend system
- `/docs`: Documentation files
Packages prefixed with `core-` (e.g., `@backstage/core-plugin-api`) are part of the old frontend system. Packages prefixed with `frontend-` (e.g., `@backstage/frontend-plugin-api`) are part of the new frontend system (NFS). Packages prefixed with `backend-` (e.g., `@backstage/backend-plugin-api`) are part of the backend system.
@@ -25,7 +25,7 @@ The search plugin is a collection of extensions that implement the search featur
### Installation
Only one step is required to start using the `Search` plugin within declarative integration, so all you have to do is to install the `@backstage/plugin-catalog` and `@backstage/plugin-search` packages, (e.g., [app-next](https://github.com/backstage/backstage/tree/master/packages/app-next)):
Only one step is required to start using the `Search` plugin within declarative integration, so all you have to do is to install the `@backstage/plugin-catalog` and `@backstage/plugin-search` packages, (e.g., [app](https://github.com/backstage/backstage/tree/master/packages/app)):
```sh
yarn add @backstage/plugin-catalog @backstage/plugin-search
+1 -1
View File
@@ -9,4 +9,4 @@ description: The Frontend System
We recommend migrating your frontend plugins to the new frontend system. If you do please do so under an `/alpha` sub-path export.
You can find an example app setup in the [`app-next` package](https://github.com/backstage/backstage/tree/master/packages/app-next).
You can find an example app setup in the [`app` package](https://github.com/backstage/backstage/tree/master/packages/app).
+1 -2
View File
@@ -35,7 +35,6 @@
"clean": "backstage-cli repo clean",
"create-plugin": "echo \"use 'yarn new' instead\"",
"dev": "echo \"use 'yarn start' instead\"",
"dev:next": "echo \"use 'yarn start:next' instead\"",
"docker-build": "yarn tsc && yarn workspace example-backend build && yarn workspace example-backend build-image",
"fix": "backstage-cli repo fix --publish",
"postinstall": "husky || true",
@@ -57,8 +56,8 @@
"start": "backstage-cli repo start",
"start-backend": "echo \"Use 'yarn start example-backend' instead\"",
"start:docker": "docker compose -f docker-compose.deps.yml up --wait && BACKSTAGE_ENV=docker yarn start",
"start:legacy": "yarn start example-app-legacy example-backend",
"start:microsite": "cd microsite/ && yarn start",
"start:next": "yarn start example-app-next example-backend",
"storybook": "storybook dev -p 6006",
"sync-issue-templates": "node ./.github/ISSUE_TEMPLATE/sync.js",
"techdocs-cli": "node scripts/techdocs-cli.js",
@@ -1,4 +1,4 @@
# app-next-example-plugin
# app-example-plugin
## 0.0.32-next.0
@@ -1,8 +1,8 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: app-next-example-plugin
title: app-next-example-plugin
name: app-example-plugin
title: app-example-plugin
description: Backstage internal example plugin
spec:
lifecycle: experimental
@@ -0,0 +1,9 @@
# Knip report
## Unused devDependencies (2)
| Name | Location | Severity |
| :---------- | :----------- | :------- |
| cross-fetch | packages/app-example-plugin/package.json | error |
| msw | packages/app-example-plugin/package.json | error |
@@ -1,12 +1,12 @@
{
"name": "app-next-example-plugin",
"name": "app-example-plugin",
"version": "0.0.32-next.0",
"description": "Backstage internal example plugin",
"backstage": {
"role": "frontend-plugin",
"pluginId": "example",
"pluginPackages": [
"app-next-example-plugin"
"app-example-plugin"
]
},
"publishConfig": {
@@ -22,7 +22,7 @@
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/app-next-example-plugin"
"directory": "packages/app-example-plugin"
},
"license": "Apache-2.0",
"sideEffects": false,
@@ -1,4 +1,4 @@
## API Report File for "app-next-example-plugin"
## API Report File for "app-example-plugin"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+5
View File
@@ -0,0 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
# example-app-legacy
This package is an example of a Backstage application using the old (legacy) frontend system.
**NOTE:** This is the legacy frontend system. For new projects, use `packages/app` which uses the new frontend system.
@@ -1,8 +1,8 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: example-app-next
title: example-app-next
name: example-app-legacy
title: example-app-legacy
spec:
lifecycle: experimental
type: backstage-frontend
+29
View File
@@ -0,0 +1,29 @@
# Knip report
## Unused dependencies (8)
| Name | Location | Severity |
| :------------------------------ | :----------- | :------- |
| @backstage/plugin-search-common | packages/app-legacy/package.json | error |
| @backstage/plugin-auth-react | packages/app-legacy/package.json | error |
| @backstage/frontend-app-api | packages/app-legacy/package.json | error |
| @material-ui/lab | packages/app-legacy/package.json | error |
| zen-observable | packages/app-legacy/package.json | error |
| @octokit/rest | packages/app-legacy/package.json | error |
| react-router | packages/app-legacy/package.json | error |
| history | packages/app-legacy/package.json | error |
## Unused devDependencies (3)
| Name | Location | Severity |
| :-------------------------- | :----------- | :------- |
| @testing-library/user-event | packages/app-legacy/package.json | error |
| @types/zen-observable | packages/app-legacy/package.json | error |
| @types/jquery | packages/app-legacy/package.json | error |
## Unlisted dependencies (1)
| Name | Location | Severity |
| :---------- | :------------------------------------------------------- | :------- |
| @rjsf/utils | packages/app/src/components/scaffolder/customScaffolderExtensions.tsx | error |
@@ -1,14 +1,15 @@
{
"name": "example-app-next",
"version": "0.0.32-next.1",
"name": "example-app-legacy",
"version": "0.2.118-next.1",
"backstage": {
"role": "frontend"
},
"private": true,
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/app-next"
"directory": "packages/app-legacy"
},
"license": "Apache-2.0",
"files": [
@@ -18,7 +19,7 @@
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"start": "backstage-cli package start --config ../../app-config.yaml --config app-config.yaml",
"start": "cross-env EXPERIMENTAL_LAZY_COMPILATION=1 backstage-cli package start",
"test": "backstage-cli package test"
},
"browserslist": {
@@ -39,18 +40,11 @@
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/core-compat-api": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-app-api": "workspace:^",
"@backstage/frontend-defaults": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-api-docs": "workspace:^",
"@backstage/plugin-app": "workspace:^",
"@backstage/plugin-app-react": "workspace:^",
"@backstage/plugin-app-visualizer": "workspace:^",
"@backstage/plugin-auth": "workspace:^",
"@backstage/plugin-auth-react": "workspace:^",
"@backstage/plugin-catalog": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
@@ -63,6 +57,7 @@
"@backstage/plugin-home-react": "workspace:^",
"@backstage/plugin-kubernetes": "workspace:^",
"@backstage/plugin-kubernetes-cluster": "workspace:^",
"@backstage/plugin-mui-to-bui": "workspace:^",
"@backstage/plugin-notifications": "workspace:^",
"@backstage/plugin-org": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
@@ -92,6 +87,7 @@
},
"devDependencies": {
"@backstage/test-utils": "workspace:^",
"@playwright/test": "^1.32.3",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^16.0.0",
@@ -100,7 +96,9 @@
"@types/react": "*",
"@types/react-dom": "*",
"@types/zen-observable": "^0.8.0",
"cross-env": "^10.0.0"
"axios": "^1.13.0",
"cross-env": "^10.0.0",
"msw": "^1.0.0"
},
"bundled": true
}

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 883 B

After

Width:  |  Height:  |  Size: 883 B

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -42,7 +42,7 @@
href="<%= publicPath %>/safari-pinned-tab.svg"
color="#5bbad5"
/>
<title><%= config.getString('app.title') %></title>
<title><%= config.getOptionalString('app.title') ?? 'Backstage' %></title>
<% if (config.has('app.datadogRum')) { %>
<script>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -14,11 +14,8 @@
* limitations under the License.
*/
import { renderWithEffects } from '@backstage/test-utils';
// Rarely, and only in windows CI, do these tests take slightly more than the
// default five seconds
jest.setTimeout(15_000);
import { render, waitFor } from '@testing-library/react';
import App from './App';
describe('App', () => {
it('should render', async () => {
@@ -44,8 +41,10 @@ describe('App', () => {
] as any,
};
const { default: app } = await import('./App');
const rendered = await renderWithEffects(app);
expect(rendered.baseElement).toBeInTheDocument();
const rendered = render(<App />);
await waitFor(() => {
expect(rendered.baseElement).toBeInTheDocument();
});
});
});
+226
View File
@@ -0,0 +1,226 @@
/*
* Copyright 2020 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 { createApp } from '@backstage/app-defaults';
import { AppRouter, FeatureFlagged, FlatRoutes } from '@backstage/core-app-api';
import {
AlertDisplay,
OAuthRequestDialog,
SignInPage,
} from '@backstage/core-components';
import { ApiExplorerPage } from '@backstage/plugin-api-docs';
import { CatalogEntityPage, CatalogIndexPage } from '@backstage/plugin-catalog';
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
import { CatalogImportPage } from '@backstage/plugin-catalog-import';
import { HomepageCompositionRoot, VisitListener } from '@backstage/plugin-home';
import { ScaffolderPage } from '@backstage/plugin-scaffolder';
import {
ScaffolderFieldExtensions,
ScaffolderLayouts,
} from '@backstage/plugin-scaffolder-react';
import { SearchPage } from '@backstage/plugin-search';
import {
TechDocsIndexPage,
TechDocsReaderPage,
} from '@backstage/plugin-techdocs';
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import {
ExpandableNavigation,
LightBox,
ReportIssue,
TextSize,
} from '@backstage/plugin-techdocs-module-addons-contrib';
import {
SettingsLayout,
UserSettingsPage,
} from '@backstage/plugin-user-settings';
import { AdvancedSettings } from './components/advancedSettings';
import AlarmIcon from '@material-ui/icons/Alarm';
import { Navigate, Route } from 'react-router-dom';
import { apis } from './apis';
import { entityPage } from './components/catalog/EntityPage';
import { Root } from './components/Root';
import { DelayingComponentFieldExtension } from './components/scaffolder/customScaffolderExtensions';
import { defaultPreviewTemplate } from './components/scaffolder/defaultPreviewTemplate';
import { searchPage } from './components/search/SearchPage';
import { providers } from './identityProviders';
import { SignalsDisplay } from '@backstage/plugin-signals';
import { techDocsPage } from './components/techdocs/TechDocsPage';
import { RequirePermission } from '@backstage/plugin-permission-react';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts';
import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
import { DevToolsPage } from '@backstage/plugin-devtools';
import { CatalogUnprocessedEntitiesPage } from '@backstage/plugin-catalog-unprocessed-entities';
import {
NotificationsPage,
UserNotificationSettingsCard,
} from '@backstage/plugin-notifications';
import { CustomizableHomePage } from './components/home/CustomizableHomePage';
import { HomePage } from './components/home/HomePage';
import { BuiThemerPage } from '@backstage/plugin-mui-to-bui';
const app = createApp({
apis,
icons: {
// Custom icon example
alert: AlarmIcon,
},
featureFlags: [
{
name: 'scaffolder-next-preview',
description: 'Preview the new Scaffolder Next',
pluginId: '',
},
],
components: {
SignInPage: props => {
return (
<SignInPage
{...props}
providers={['guest', 'custom', ...providers]}
title="Select a sign-in method"
align="center"
/>
);
},
},
});
const routes = (
<FlatRoutes>
<Route path="/" element={<Navigate to="catalog" />} />
{/* TODO(rubenl): Move this to / once its more mature and components exist */}
<FeatureFlagged with="customizable-home-page-preview">
<Route path="/home" element={<HomepageCompositionRoot />}>
<CustomizableHomePage />
</Route>
</FeatureFlagged>
<FeatureFlagged without="customizable-home-page-preview">
<Route path="/home" element={<HomepageCompositionRoot />}>
<HomePage />
</Route>
</FeatureFlagged>
<Route
path="/catalog"
element={<CatalogIndexPage pagination={{ mode: 'offset', limit: 20 }} />}
/>
<Route
path="/catalog/:namespace/:kind/:name"
element={<CatalogEntityPage />}
>
{entityPage}
</Route>
<Route
path="/catalog-unprocessed-entities"
element={<CatalogUnprocessedEntitiesPage />}
/>
<Route
path="/catalog-import"
element={
<RequirePermission permission={catalogEntityCreatePermission}>
<CatalogImportPage />
</RequirePermission>
}
/>
<Route
path="/catalog-graph"
element={
<CatalogGraphPage
initialState={{
selectedKinds: ['component', 'domain', 'system', 'api', 'group'],
}}
/>
}
/>
<Route
path="/docs"
element={<TechDocsIndexPage pagination={{ mode: 'offset', limit: 20 }} />}
/>
<Route
path="/docs/:namespace/:kind/:name/*"
element={<TechDocsReaderPage />}
>
{techDocsPage}
<TechDocsAddons>
<ExpandableNavigation />
<ReportIssue />
<TextSize />
<LightBox />
</TechDocsAddons>
</Route>
<Route
path="/create"
element={
<ScaffolderPage
defaultPreviewTemplate={defaultPreviewTemplate}
groups={[
{
title: 'Recommended',
filter: entity =>
entity?.metadata?.tags?.includes('recommended') ?? false,
},
]}
/>
}
>
<ScaffolderFieldExtensions>
<DelayingComponentFieldExtension />
</ScaffolderFieldExtensions>
<ScaffolderLayouts>
<TwoColumnLayout />
</ScaffolderLayouts>
</Route>
<Route path="/api-docs" element={<ApiExplorerPage />} />
<Route path="/search" element={<SearchPage />}>
{searchPage}
</Route>
<Route path="/settings" element={<UserSettingsPage />}>
<SettingsLayout.Route path="/advanced" title="Advanced">
<AdvancedSettings />
</SettingsLayout.Route>
<SettingsLayout.Route path="/notifications" title="Notifications">
<UserNotificationSettingsCard
originNames={{ 'plugin:scaffolder': 'Scaffolder' }}
/>
</SettingsLayout.Route>
</Route>
<Route path="/devtools" element={<DevToolsPage />}>
{customDevToolsPage}
</Route>
<Route path="/notifications" element={<NotificationsPage />} />
<Route path="/mui-to-bui" element={<BuiThemerPage />} />
</FlatRoutes>
);
export default app.createRoot(
<>
<AlertDisplay transientTimeoutMs={2500} />
<OAuthRequestDialog />
<SignalsDisplay />
<AppRouter>
<VisitListener />
<Root>{routes}</Root>
</AppRouter>
</>,
);
@@ -0,0 +1,67 @@
/*
* Copyright 2020 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 { createApp } from '@backstage/app-defaults';
import { AppRouter } from '@backstage/core-app-api';
import {
AlertDisplay,
OAuthRequestDialog,
SignInPage,
} from '@backstage/core-components';
import { CookieAuthRedirect } from '@backstage/plugin-auth-react';
import ReactDOM from 'react-dom/client';
import { providers } from '../src/identityProviders';
import {
configApiRef,
createApiFactory,
discoveryApiRef,
} from '@backstage/core-plugin-api';
import { AuthProxyDiscoveryApi } from '../src/AuthProxyDiscoveryApi';
import '@backstage/ui/css/styles.css';
const app = createApp({
apis: [
createApiFactory({
api: discoveryApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) => AuthProxyDiscoveryApi.fromConfig(configApi),
}),
],
components: {
SignInPage: props => {
return (
<SignInPage
{...props}
providers={['guest', 'custom', ...providers]}
title="Select a sign-in method"
align="center"
/>
);
},
},
});
const App = app.createRoot(
<>
<AlertDisplay transientTimeoutMs={2500} />
<OAuthRequestDialog />
<AppRouter>
<CookieAuthRedirect />
</AppRouter>
</>,
);
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
@@ -16,7 +16,7 @@
import '@backstage/cli/asset-types';
import ReactDOM from 'react-dom/client';
import app from './App';
import App from './App';
import '@backstage/ui/css/styles.css';
ReactDOM.createRoot(document.getElementById('root')!).render(app);
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
@@ -1,9 +0,0 @@
# Knip report
## Unused devDependencies (2)
| Name | Location | Severity |
| :---------- | :----------- | :------- |
| cross-fetch | packages/app-next-example-plugin/package.json | error |
| msw | packages/app-next-example-plugin/package.json | error |
-5
View File
@@ -1,5 +0,0 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
File diff suppressed because it is too large Load Diff
-7
View File
@@ -1,7 +0,0 @@
# example-app-next
This package is an example of a Backstage application using the [new frontend](../../docs/frontend-system/index.md).
To play with it, open a terminal and run the command: `yarn start`
**NOTE:** Don't forget to open a second terminal and to launch the backend there, using `yarn start`! The frontend requires a backend to connect to.
-43
View File
@@ -1,43 +0,0 @@
# Knip report
## Unused dependencies (26)
| Name | Location | Severity |
| :----------------------------------------------- | :----------- | :------- |
| @backstage/plugin-techdocs-module-addons-contrib | packages/app-next/package.json | error |
| @backstage/plugin-catalog-unprocessed-entities | packages/app-next/package.json | error |
| @backstage/plugin-kubernetes-cluster | packages/app-next/package.json | error |
| @backstage/plugin-permission-react | packages/app-next/package.json | error |
| @backstage/plugin-scaffolder-react | packages/app-next/package.json | error |
| @backstage/plugin-catalog-common | packages/app-next/package.json | error |
| @backstage/plugin-techdocs-react | packages/app-next/package.json | error |
| @backstage/plugin-catalog-graph | packages/app-next/package.json | error |
| @backstage/plugin-search-common | packages/app-next/package.json | error |
| @backstage/plugin-search-react | packages/app-next/package.json | error |
| @backstage/integration-react | packages/app-next/package.json | error |
| @backstage/plugin-auth-react | packages/app-next/package.json | error |
| @backstage/plugin-scaffolder | packages/app-next/package.json | error |
| @backstage/core-plugin-api | packages/app-next/package.json | error |
| @backstage/plugin-api-docs | packages/app-next/package.json | error |
| @backstage/plugin-catalog | packages/app-next/package.json | error |
| @backstage/plugin-signals | packages/app-next/package.json | error |
| @backstage/app-defaults | packages/app-next/package.json | error |
| @backstage/plugin-app | packages/app-next/package.json | error |
| @backstage/plugin-org | packages/app-next/package.json | error |
| @backstage/config | packages/app-next/package.json | error |
| @material-ui/lab | packages/app-next/package.json | error |
| zen-observable | packages/app-next/package.json | error |
| @octokit/rest | packages/app-next/package.json | error |
| react-use | packages/app-next/package.json | error |
| history | packages/app-next/package.json | error |
## Unused devDependencies (5)
| Name | Location | Severity |
| :-------------------------- | :----------- | :------- |
| @testing-library/user-event | packages/app-next/package.json | error |
| @types/zen-observable | packages/app-next/package.json | error |
| @testing-library/dom | packages/app-next/package.json | error |
| @types/jquery | packages/app-next/package.json | error |
| cross-env | packages/app-next/package.json | error |
-244
View File
@@ -1,244 +0,0 @@
/*
* Copyright 2023 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 { createApp } from '@backstage/frontend-defaults';
import { pagesPlugin } from './examples/pagesPlugin';
import notFoundErrorPage from './examples/notFoundErrorPageExtension';
import userSettingsPlugin from '@backstage/plugin-user-settings/alpha';
import homePlugin from '@backstage/plugin-home/alpha';
import { createFrontendModule } from '@backstage/frontend-plugin-api';
import {
HomePageLayoutBlueprint,
type HomePageLayoutProps,
} from '@backstage/plugin-home-react/alpha';
import { Fragment } from 'react';
import { Content, Header, Page } from '@backstage/core-components';
import {
CustomHomepageGrid,
WelcomeTitle,
HeaderWorldClock,
type ClockConfig,
} from '@backstage/plugin-home';
import {
techdocsPlugin,
TechDocsIndexPage,
TechDocsReaderPage,
EntityTechdocsContent,
} from '@backstage/plugin-techdocs';
import appVisualizerPlugin from '@backstage/plugin-app-visualizer';
import { convertLegacyAppRoot } from '@backstage/core-compat-api';
import { FlatRoutes } from '@backstage/core-app-api';
import { Route } from 'react-router';
import { CatalogImportPage } from '@backstage/plugin-catalog-import';
import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha';
import { convertLegacyPlugin } from '@backstage/core-compat-api';
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 { unprocessedEntitiesDevToolsContent } from '@backstage/plugin-catalog-unprocessed-entities/alpha';
import catalogPlugin from '@backstage/plugin-catalog/alpha';
import InfoIcon from '@material-ui/icons/Info';
/*
# Notes
TODO:
- proper createApp
- connect extensions and plugins, provide method?
- higher level API for creating standard extensions + higher order framework API for creating those?
- extension config schema + validation
- figure out how to resolve configured extension ref to runtime value, e.g. '@backstage/plugin-graphiql#GraphiqlPage'
- make sure all shorthands work + tests
- figure out package structure / how to ship, frontend-plugin-api/frontend-app-api
- figure out routing, useRouteRef in the new system
- Legacy plugins / interop
- dynamic updates, runtime API
*/
/* core */
// const discoverPackages = async () => {
// // stub for now, deferring package discovery til later
// return ['@backstage/plugin-graphiql'];
// };
/* graphiql package */
/* app.tsx */
/**
* TechDocs does support the new frontend system so this conversion is not
* strictly necessary, but it's left here to provide a demo of the utilities for
* converting legacy plugins.
*/
const convertedTechdocsPlugin = convertLegacyPlugin(techdocsPlugin, {
extensions: [
// TODO: We likely also need a way to convert an entire <Route> tree similar to collectLegacyRoutes
convertLegacyPageExtension(TechDocsIndexPage, {
name: 'index',
path: '/docs',
}),
convertLegacyPageExtension(TechDocsReaderPage, {
path: '/docs/:namespace/:kind/:name/*',
}),
convertLegacyEntityContentExtension(EntityTechdocsContent),
],
});
const clockConfigs: ClockConfig[] = [
{ label: 'NYC', timeZone: 'America/New_York' },
{ label: 'UTC', timeZone: 'UTC' },
{ label: 'STO', timeZone: 'Europe/Stockholm' },
{ label: 'TYO', timeZone: 'Asia/Tokyo' },
];
const customHomePageModule = createFrontendModule({
pluginId: 'home',
extensions: [
HomePageLayoutBlueprint.make({
params: {
loader: async () =>
function CustomHomePageLayout({ widgets }: HomePageLayoutProps) {
return (
<Page themeId="home">
<Header title={<WelcomeTitle />} pageTitleOverride="Home">
<HeaderWorldClock clockConfigs={clockConfigs} />
</Header>
<Content>
<CustomHomepageGrid>
{widgets.map((widget, index) => (
<Fragment key={widget.name ?? index}>
{widget.component}
</Fragment>
))}
</CustomHomepageGrid>
</Content>
</Page>
);
},
},
}),
],
});
// customize catalog example
const customizedCatalog = catalogPlugin.withOverrides({
extensions: [
catalogPlugin.getExtension('entity-content:catalog/overview').override({
params: {
icon: <InfoIcon />,
},
}),
],
});
const notFoundErrorPageModule = createFrontendModule({
pluginId: 'app',
extensions: [notFoundErrorPage],
});
const devtoolsPluginUnprocessed = createFrontendModule({
pluginId: 'catalog-unprocessed-entities',
extensions: [unprocessedEntitiesDevToolsContent],
});
const collectedLegacyPlugins = convertLegacyAppRoot(
<FlatRoutes>
<Route path="/catalog-import" element={<CatalogImportPage />} />
</FlatRoutes>,
);
const app = createApp({
features: [
customizedCatalog,
pagesPlugin,
convertedTechdocsPlugin,
userSettingsPlugin,
homePlugin,
appVisualizerPlugin,
kubernetesPlugin,
notFoundErrorPageModule,
appModuleNav,
customHomePageModule,
devtoolsPlugin,
devtoolsPluginUnprocessed,
...collectedLegacyPlugins,
],
advanced: {
pluginInfoResolver,
},
/* Handled through config instead */
// bindRoutes({ bind }) {
// bind(pagesPlugin.externalRoutes, { pageX: pagesPlugin.routes.pageX });
// },
});
// const legacyApp = createLegacyApp({ plugins: [legacyGraphiqlPlugin] });
export default app.createRoot();
// const routes = (
// <FlatRoutes>
// {/* <Route path="/" element={<Navigate to="catalog" />} />
// <Route path="/catalog" element={<CatalogIndexPage />} />
// <Route
// path="/catalog/:namespace/:kind/:name"
// element={<CatalogEntityPage />}
// >
// <EntityLayout>
// <EntityLayout.Route path="/" title="Overview">
// <Grid container spacing={3} alignItems="stretch">
// <Grid item md={6} xs={12}>
// <EntityAboutCard variant="gridItem" />
// </Grid>
// <Grid item md={4} xs={12}>
// <EntityLinksCard />
// </Grid>
// </Grid>
// </EntityLayout.Route>
// <EntityLayout.Route path="/todos" title="TODOs">
// <EntityTodoContent />
// </EntityLayout.Route>
// </EntityLayout>
// </Route>
// <Route
// path="/catalog-import"
// element={
// <CatalogImportPage />
// }
// /> */}
// {/* <Route
// path="/tech-radar"
// element={<TechRadarPage width={1500} height={800} />}
// /> */}
// <Route path="/graphiql" element={<GraphiQLPage />} />
// </FlatRoutes>
// );
// export default app.createRoot(
// <>
// {/* <AlertDisplay transientTimeoutMs={2500} />
// <OAuthRequestDialog /> */}
// <AppRouter>{routes}</AppRouter>
// </>,
// );
@@ -1,27 +0,0 @@
/*
* Copyright 2020 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 ReactDOM from 'react-dom/client';
import { createApp } from '@backstage/frontend-defaults';
import { appModulePublicSignIn } from '@backstage/plugin-app/alpha';
import '@backstage/ui/css/styles.css';
const app = createApp({
features: [appModulePublicSignIn],
});
ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot());
+3 -3
View File
@@ -1,5 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
+660 -7743
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -1,3 +1,7 @@
# example-app
This package is an example of a Backstage application.
This package is the main example Backstage application using the [new frontend system](../../docs/frontend-system/index.md).
To play with it, open a terminal and run the command: `yarn start`
**NOTE:** Don't forget to open a second terminal and to launch the backend there, using `yarn start`! The frontend requires a backend to connect to.
@@ -10,7 +10,7 @@ app:
- match:
pluginId: pages
info:
description: 'This description was overridden in packages/app-next/app-config.yaml'
description: 'This description was overridden in packages/app/app-config.yaml'
- match:
pluginId: /^catalog(-.*)?$/
info:

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

+6 -3
View File
@@ -35,14 +35,17 @@ test('Should not throw `ResizeObserver loop completed with undelivered notificat
).not.toBeVisible();
});
test('Should render some home page widgets', async ({ page }) => {
test('Should render the home page', async ({ page }) => {
await page.goto('/');
const enterButton = page.getByRole('button', { name: 'Enter' });
await expect(enterButton).toBeVisible();
await enterButton.click();
// Wait for sign-in to complete
await expect(page.getByRole('link', { name: 'Catalog' })).toBeVisible();
await page.goto('/home');
await expect(page.getByText('Top Visited')).toBeVisible();
await expect(page.getByText('Recently Visited')).toBeVisible();
// The home page should render with the custom homepage grid
await expect(page.getByRole('link', { name: 'Home' })).toBeVisible();
});
+9 -2
View File
@@ -23,8 +23,11 @@ test('the results are rendered as expected', async ({ page }) => {
await expect(enterButton).toBeVisible();
await enterButton.click();
await page.goto('/search');
await page.route(`http://*/api/search/query?term=*`, async route => {
// Wait for sign-in to complete before navigating
await expect(page.getByRole('link', { name: 'Catalog' })).toBeVisible();
// Set up route interception BEFORE navigating to the search page
await page.route(`**/api/search/query?term=*`, async route => {
const results = [
{
type: 'software-catalog',
@@ -38,9 +41,13 @@ test('the results are rendered as expected', async ({ page }) => {
await route.fulfill({ json: { results } });
});
await page.goto('/search');
await expect(
page.getByPlaceholder('Search in Backstage Example App'),
).toBeVisible();
// Type a search query to trigger the mocked response
await page.getByPlaceholder('Search in Backstage Example App').fill('test');
await expect(page.getByText('Backstage system documentation')).toBeVisible();
});
+3 -7
View File
@@ -23,11 +23,7 @@ test('App should render the welcome page', async ({ page }) => {
await expect(enterButton).toBeVisible();
await enterButton.click();
await expect(page.getByText('My Company Catalog')).toBeVisible();
const supportButton = page.getByTestId('support-button');
await expect(supportButton).toBeVisible();
await supportButton.click();
await expect(page.getByText('#backstage')).toBeVisible();
// Verify the sidebar navigation is visible after sign-in
await expect(page.getByRole('link', { name: 'Catalog' })).toBeVisible();
await expect(page.getByRole('link', { name: 'APIs' })).toBeVisible();
});
+32 -18
View File
@@ -1,29 +1,43 @@
# Knip report
## Unused dependencies (8)
## Unused dependencies (26)
| Name | Location | Severity |
| :------------------------------ | :----------- | :------- |
| @backstage/plugin-search-common | packages/app/package.json | error |
| @backstage/plugin-auth-react | packages/app/package.json | error |
| @backstage/frontend-app-api | packages/app/package.json | error |
| @material-ui/lab | packages/app/package.json | error |
| zen-observable | packages/app/package.json | error |
| @octokit/rest | packages/app/package.json | error |
| react-router | packages/app/package.json | error |
| history | packages/app/package.json | error |
| Name | Location | Severity |
| :----------------------------------------------- | :----------- | :------- |
| @backstage/plugin-techdocs-module-addons-contrib | packages/app/package.json | error |
| @backstage/plugin-catalog-unprocessed-entities | packages/app/package.json | error |
| @backstage/plugin-kubernetes-cluster | packages/app/package.json | error |
| @backstage/plugin-permission-react | packages/app/package.json | error |
| @backstage/plugin-scaffolder-react | packages/app/package.json | error |
| @backstage/plugin-catalog-common | packages/app/package.json | error |
| @backstage/plugin-techdocs-react | packages/app/package.json | error |
| @backstage/plugin-catalog-graph | packages/app/package.json | error |
| @backstage/plugin-search-common | packages/app/package.json | error |
| @backstage/plugin-search-react | packages/app/package.json | error |
| @backstage/integration-react | packages/app/package.json | error |
| @backstage/plugin-auth-react | packages/app/package.json | error |
| @backstage/plugin-scaffolder | packages/app/package.json | error |
| @backstage/core-plugin-api | packages/app/package.json | error |
| @backstage/plugin-api-docs | packages/app/package.json | error |
| @backstage/plugin-catalog | packages/app/package.json | error |
| @backstage/plugin-signals | packages/app/package.json | error |
| @backstage/app-defaults | packages/app/package.json | error |
| @backstage/plugin-app | packages/app/package.json | error |
| @backstage/plugin-org | packages/app/package.json | error |
| @backstage/config | packages/app/package.json | error |
| @material-ui/lab | packages/app/package.json | error |
| zen-observable | packages/app/package.json | error |
| @octokit/rest | packages/app/package.json | error |
| react-use | packages/app/package.json | error |
| history | packages/app/package.json | error |
## Unused devDependencies (3)
## Unused devDependencies (5)
| Name | Location | Severity |
| :-------------------------- | :----------- | :------- |
| @testing-library/user-event | packages/app/package.json | error |
| @types/zen-observable | packages/app/package.json | error |
| @testing-library/dom | packages/app/package.json | error |
| @types/jquery | packages/app/package.json | error |
## Unlisted dependencies (1)
| Name | Location | Severity |
| :---------- | :------------------------------------------------------- | :------- |
| @rjsf/utils | packages/app/src/components/scaffolder/customScaffolderExtensions.tsx | error |
| cross-env | packages/app/package.json | error |
+11 -7
View File
@@ -1,6 +1,6 @@
{
"name": "example-app",
"version": "0.2.118-next.1",
"version": "0.0.32-next.1",
"backstage": {
"role": "frontend"
},
@@ -19,7 +19,7 @@
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"start": "cross-env EXPERIMENTAL_LAZY_COMPILATION=1 backstage-cli package start",
"start": "backstage-cli package start --config ../../app-config.yaml --config app-config.yaml",
"test": "backstage-cli package test"
},
"browserslist": {
@@ -40,11 +40,18 @@
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/core-compat-api": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-app-api": "workspace:^",
"@backstage/frontend-defaults": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-api-docs": "workspace:^",
"@backstage/plugin-app": "workspace:^",
"@backstage/plugin-app-react": "workspace:^",
"@backstage/plugin-app-visualizer": "workspace:^",
"@backstage/plugin-auth": "workspace:^",
"@backstage/plugin-auth-react": "workspace:^",
"@backstage/plugin-catalog": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
@@ -56,7 +63,6 @@
"@backstage/plugin-home": "workspace:^",
"@backstage/plugin-kubernetes": "workspace:^",
"@backstage/plugin-kubernetes-cluster": "workspace:^",
"@backstage/plugin-mui-to-bui": "workspace:^",
"@backstage/plugin-notifications": "workspace:^",
"@backstage/plugin-org": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
@@ -86,7 +92,7 @@
},
"devDependencies": {
"@backstage/test-utils": "workspace:^",
"@playwright/test": "^1.32.3",
"@playwright/test": "^1.58.2",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^16.0.0",
@@ -95,9 +101,7 @@
"@types/react": "*",
"@types/react-dom": "*",
"@types/zen-observable": "^0.8.0",
"axios": "^1.13.0",
"cross-env": "^10.0.0",
"msw": "^1.0.0"
"cross-env": "^10.0.0"
},
"bundled": true
}
+1 -1
View File
@@ -42,7 +42,7 @@
href="<%= publicPath %>/safari-pinned-tab.svg"
color="#5bbad5"
/>
<title><%= config.getOptionalString('app.title') ?? 'Backstage' %></title>
<title><%= config.getString('app.title') %></title>
<% if (config.has('app.datadogRum')) { %>
<script>
+8 -7
View File
@@ -14,8 +14,11 @@
* limitations under the License.
*/
import { render, waitFor } from '@testing-library/react';
import App from './App';
import { renderWithEffects } from '@backstage/test-utils';
// Rarely, and only in windows CI, do these tests take slightly more than the
// default five seconds
jest.setTimeout(15_000);
describe('App', () => {
it('should render', async () => {
@@ -41,10 +44,8 @@ describe('App', () => {
] as any,
};
const rendered = render(<App />);
await waitFor(() => {
expect(rendered.baseElement).toBeInTheDocument();
});
const { default: app } = await import('./App');
const rendered = await renderWithEffects(app);
expect(rendered.baseElement).toBeInTheDocument();
});
});
+216 -198
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2020 The Backstage Authors
* Copyright 2023 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.
@@ -14,213 +14,231 @@
* limitations under the License.
*/
import { createApp } from '@backstage/app-defaults';
import { AppRouter, FeatureFlagged, FlatRoutes } from '@backstage/core-app-api';
import {
AlertDisplay,
OAuthRequestDialog,
SignInPage,
} from '@backstage/core-components';
import { ApiExplorerPage } from '@backstage/plugin-api-docs';
import { CatalogEntityPage, CatalogIndexPage } from '@backstage/plugin-catalog';
import { createApp } from '@backstage/frontend-defaults';
import { pagesPlugin } from './examples/pagesPlugin';
import notFoundErrorPage from './examples/notFoundErrorPageExtension';
import userSettingsPlugin from '@backstage/plugin-user-settings/alpha';
import homePlugin from '@backstage/plugin-home/alpha';
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
import { CatalogImportPage } from '@backstage/plugin-catalog-import';
import { HomepageCompositionRoot, VisitListener } from '@backstage/plugin-home';
import { ScaffolderPage } from '@backstage/plugin-scaffolder';
import { createFrontendModule } from '@backstage/frontend-plugin-api';
import {
ScaffolderFieldExtensions,
ScaffolderLayouts,
} from '@backstage/plugin-scaffolder-react';
import { SearchPage } from '@backstage/plugin-search';
HomePageLayoutBlueprint,
type HomePageLayoutProps,
} from '@backstage/plugin-home-react/alpha';
import { Fragment } from 'react';
import { Content, Header, Page } from '@backstage/core-components';
import {
CustomHomepageGrid,
WelcomeTitle,
HeaderWorldClock,
type ClockConfig,
} from '@backstage/plugin-home';
import {
techdocsPlugin,
TechDocsIndexPage,
TechDocsReaderPage,
EntityTechdocsContent,
} from '@backstage/plugin-techdocs';
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import {
ExpandableNavigation,
LightBox,
ReportIssue,
TextSize,
} from '@backstage/plugin-techdocs-module-addons-contrib';
import {
SettingsLayout,
UserSettingsPage,
} from '@backstage/plugin-user-settings';
import { AdvancedSettings } from './components/advancedSettings';
import AlarmIcon from '@material-ui/icons/Alarm';
import { Navigate, Route } from 'react-router-dom';
import { apis } from './apis';
import { entityPage } from './components/catalog/EntityPage';
import { Root } from './components/Root';
import { DelayingComponentFieldExtension } from './components/scaffolder/customScaffolderExtensions';
import { defaultPreviewTemplate } from './components/scaffolder/defaultPreviewTemplate';
import { searchPage } from './components/search/SearchPage';
import { providers } from './identityProviders';
import { SignalsDisplay } from '@backstage/plugin-signals';
import { techDocsPage } from './components/techdocs/TechDocsPage';
import { RequirePermission } from '@backstage/plugin-permission-react';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts';
import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
import { DevToolsPage } from '@backstage/plugin-devtools';
import { CatalogUnprocessedEntitiesPage } from '@backstage/plugin-catalog-unprocessed-entities';
import {
NotificationsPage,
UserNotificationSettingsCard,
} from '@backstage/plugin-notifications';
import { CustomizableHomePage } from './components/home/CustomizableHomePage';
import { HomePage } from './components/home/HomePage';
import { BuiThemerPage } from '@backstage/plugin-mui-to-bui';
import appVisualizerPlugin from '@backstage/plugin-app-visualizer';
import { convertLegacyAppRoot } from '@backstage/core-compat-api';
import { FlatRoutes } from '@backstage/core-app-api';
import { Route } from 'react-router';
import { CatalogImportPage } from '@backstage/plugin-catalog-import';
import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha';
import { convertLegacyPlugin } from '@backstage/core-compat-api';
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 { unprocessedEntitiesDevToolsContent } from '@backstage/plugin-catalog-unprocessed-entities/alpha';
import catalogPlugin from '@backstage/plugin-catalog/alpha';
import InfoIcon from '@material-ui/icons/Info';
const app = createApp({
apis,
icons: {
// Custom icon example
alert: AlarmIcon,
},
featureFlags: [
{
name: 'scaffolder-next-preview',
description: 'Preview the new Scaffolder Next',
pluginId: '',
},
/*
# Notes
TODO:
- proper createApp
- connect extensions and plugins, provide method?
- higher level API for creating standard extensions + higher order framework API for creating those?
- extension config schema + validation
- figure out how to resolve configured extension ref to runtime value, e.g. '@backstage/plugin-graphiql#GraphiqlPage'
- make sure all shorthands work + tests
- figure out package structure / how to ship, frontend-plugin-api/frontend-app-api
- figure out routing, useRouteRef in the new system
- Legacy plugins / interop
- dynamic updates, runtime API
*/
/* core */
// const discoverPackages = async () => {
// // stub for now, deferring package discovery til later
// return ['@backstage/plugin-graphiql'];
// };
/* graphiql package */
/* app.tsx */
/**
* TechDocs does support the new frontend system so this conversion is not
* strictly necessary, but it's left here to provide a demo of the utilities for
* converting legacy plugins.
*/
const convertedTechdocsPlugin = convertLegacyPlugin(techdocsPlugin, {
extensions: [
// TODO: We likely also need a way to convert an entire <Route> tree similar to collectLegacyRoutes
convertLegacyPageExtension(TechDocsIndexPage, {
name: 'index',
path: '/docs',
}),
convertLegacyPageExtension(TechDocsReaderPage, {
path: '/docs/:namespace/:kind/:name/*',
}),
convertLegacyEntityContentExtension(EntityTechdocsContent),
],
components: {
SignInPage: props => {
return (
<SignInPage
{...props}
providers={['guest', 'custom', ...providers]}
title="Select a sign-in method"
align="center"
/>
);
},
},
});
const routes = (
const clockConfigs: ClockConfig[] = [
{ label: 'NYC', timeZone: 'America/New_York' },
{ label: 'UTC', timeZone: 'UTC' },
{ label: 'STO', timeZone: 'Europe/Stockholm' },
{ label: 'TYO', timeZone: 'Asia/Tokyo' },
];
const customHomePageModule = createFrontendModule({
pluginId: 'home',
extensions: [
HomePageLayoutBlueprint.make({
params: {
loader: async () =>
function CustomHomePageLayout({ widgets }: HomePageLayoutProps) {
return (
<Page themeId="home">
<Header title={<WelcomeTitle />} pageTitleOverride="Home">
<HeaderWorldClock clockConfigs={clockConfigs} />
</Header>
<Content>
<CustomHomepageGrid>
{widgets.map((widget, index) => (
<Fragment key={widget.name ?? index}>
{widget.component}
</Fragment>
))}
</CustomHomepageGrid>
</Content>
</Page>
);
},
},
}),
],
});
// customize catalog example
const customizedCatalog = catalogPlugin.withOverrides({
extensions: [
catalogPlugin.getExtension('entity-content:catalog/overview').override({
params: {
icon: <InfoIcon />,
},
}),
],
});
const notFoundErrorPageModule = createFrontendModule({
pluginId: 'app',
extensions: [notFoundErrorPage],
});
const devtoolsPluginUnprocessed = createFrontendModule({
pluginId: 'catalog-unprocessed-entities',
extensions: [unprocessedEntitiesDevToolsContent],
});
const collectedLegacyPlugins = convertLegacyAppRoot(
<FlatRoutes>
<Route path="/" element={<Navigate to="catalog" />} />
{/* TODO(rubenl): Move this to / once its more mature and components exist */}
<FeatureFlagged with="customizable-home-page-preview">
<Route path="/home" element={<HomepageCompositionRoot />}>
<CustomizableHomePage />
</Route>
</FeatureFlagged>
<FeatureFlagged without="customizable-home-page-preview">
<Route path="/home" element={<HomepageCompositionRoot />}>
<HomePage />
</Route>
</FeatureFlagged>
<Route
path="/catalog"
element={<CatalogIndexPage pagination={{ mode: 'offset', limit: 20 }} />}
/>
<Route
path="/catalog/:namespace/:kind/:name"
element={<CatalogEntityPage />}
>
{entityPage}
</Route>
<Route
path="/catalog-unprocessed-entities"
element={<CatalogUnprocessedEntitiesPage />}
/>
<Route
path="/catalog-import"
element={
<RequirePermission permission={catalogEntityCreatePermission}>
<CatalogImportPage />
</RequirePermission>
}
/>
<Route
path="/catalog-graph"
element={
<CatalogGraphPage
initialState={{
selectedKinds: ['component', 'domain', 'system', 'api', 'group'],
}}
/>
}
/>
<Route
path="/docs"
element={<TechDocsIndexPage pagination={{ mode: 'offset', limit: 20 }} />}
/>
<Route
path="/docs/:namespace/:kind/:name/*"
element={<TechDocsReaderPage />}
>
{techDocsPage}
<TechDocsAddons>
<ExpandableNavigation />
<ReportIssue />
<TextSize />
<LightBox />
</TechDocsAddons>
</Route>
<Route
path="/create"
element={
<ScaffolderPage
defaultPreviewTemplate={defaultPreviewTemplate}
groups={[
{
title: 'Recommended',
filter: entity =>
entity?.metadata?.tags?.includes('recommended') ?? false,
},
]}
/>
}
>
<ScaffolderFieldExtensions>
<DelayingComponentFieldExtension />
</ScaffolderFieldExtensions>
<ScaffolderLayouts>
<TwoColumnLayout />
</ScaffolderLayouts>
</Route>
<Route path="/api-docs" element={<ApiExplorerPage />} />
<Route path="/search" element={<SearchPage />}>
{searchPage}
</Route>
<Route path="/settings" element={<UserSettingsPage />}>
<SettingsLayout.Route path="/advanced" title="Advanced">
<AdvancedSettings />
</SettingsLayout.Route>
<SettingsLayout.Route path="/notifications" title="Notifications">
<UserNotificationSettingsCard
originNames={{ 'plugin:scaffolder': 'Scaffolder' }}
/>
</SettingsLayout.Route>
</Route>
<Route path="/devtools" element={<DevToolsPage />}>
{customDevToolsPage}
</Route>
<Route path="/notifications" element={<NotificationsPage />} />
<Route path="/mui-to-bui" element={<BuiThemerPage />} />
</FlatRoutes>
<Route path="/catalog-import" element={<CatalogImportPage />} />
</FlatRoutes>,
);
export default app.createRoot(
<>
<AlertDisplay transientTimeoutMs={2500} />
<OAuthRequestDialog />
<SignalsDisplay />
<AppRouter>
<VisitListener />
<Root>{routes}</Root>
</AppRouter>
</>,
);
const app = createApp({
features: [
customizedCatalog,
pagesPlugin,
convertedTechdocsPlugin,
userSettingsPlugin,
homePlugin,
appVisualizerPlugin,
kubernetesPlugin,
notFoundErrorPageModule,
appModuleNav,
customHomePageModule,
devtoolsPlugin,
devtoolsPluginUnprocessed,
...collectedLegacyPlugins,
],
advanced: {
pluginInfoResolver,
},
/* Handled through config instead */
// bindRoutes({ bind }) {
// bind(pagesPlugin.externalRoutes, { pageX: pagesPlugin.routes.pageX });
// },
});
// const legacyApp = createLegacyApp({ plugins: [legacyGraphiqlPlugin] });
export default app.createRoot();
// const routes = (
// <FlatRoutes>
// {/* <Route path="/" element={<Navigate to="catalog" />} />
// <Route path="/catalog" element={<CatalogIndexPage />} />
// <Route
// path="/catalog/:namespace/:kind/:name"
// element={<CatalogEntityPage />}
// >
// <EntityLayout>
// <EntityLayout.Route path="/" title="Overview">
// <Grid container spacing={3} alignItems="stretch">
// <Grid item md={6} xs={12}>
// <EntityAboutCard variant="gridItem" />
// </Grid>
// <Grid item md={4} xs={12}>
// <EntityLinksCard />
// </Grid>
// </Grid>
// </EntityLayout.Route>
// <EntityLayout.Route path="/todos" title="TODOs">
// <EntityTodoContent />
// </EntityLayout.Route>
// </EntityLayout>
// </Route>
// <Route
// path="/catalog-import"
// element={
// <CatalogImportPage />
// }
// /> */}
// {/* <Route
// path="/tech-radar"
// element={<TechRadarPage width={1500} height={800} />}
// /> */}
// <Route path="/graphiql" element={<GraphiQLPage />} />
// </FlatRoutes>
// );
// export default app.createRoot(
// <>
// {/* <AlertDisplay transientTimeoutMs={2500} />
// <OAuthRequestDialog /> */}
// <AppRouter>{routes}</AppRouter>
// </>,
// );
+5 -45
View File
@@ -14,54 +14,14 @@
* limitations under the License.
*/
import { createApp } from '@backstage/app-defaults';
import { AppRouter } from '@backstage/core-app-api';
import {
AlertDisplay,
OAuthRequestDialog,
SignInPage,
} from '@backstage/core-components';
import { CookieAuthRedirect } from '@backstage/plugin-auth-react';
import ReactDOM from 'react-dom/client';
import { providers } from '../src/identityProviders';
import {
configApiRef,
createApiFactory,
discoveryApiRef,
} from '@backstage/core-plugin-api';
import { AuthProxyDiscoveryApi } from '../src/AuthProxyDiscoveryApi';
import { createApp } from '@backstage/frontend-defaults';
import { appModulePublicSignIn } from '@backstage/plugin-app/alpha';
import '@backstage/ui/css/styles.css';
const app = createApp({
apis: [
createApiFactory({
api: discoveryApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) => AuthProxyDiscoveryApi.fromConfig(configApi),
}),
],
components: {
SignInPage: props => {
return (
<SignInPage
{...props}
providers={['guest', 'custom', ...providers]}
title="Select a sign-in method"
align="center"
/>
);
},
},
features: [appModulePublicSignIn],
});
const App = app.createRoot(
<>
<AlertDisplay transientTimeoutMs={2500} />
<OAuthRequestDialog />
<AppRouter>
<CookieAuthRedirect />
</AppRouter>
</>,
);
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot());
+2 -2
View File
@@ -16,7 +16,7 @@
import '@backstage/cli/asset-types';
import ReactDOM from 'react-dom/client';
import App from './App';
import app from './App';
import '@backstage/ui/css/styles.css';
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
ReactDOM.createRoot(document.getElementById('root')!).render(app);
@@ -31,8 +31,8 @@ const execAsync = promisify(exec);
const EXCLUDE = [
'packages/app',
'packages/app-next',
'packages/app-next-example-plugin',
'packages/app-legacy',
'packages/app-example-plugin',
'packages/cli',
'packages/cli-common',
'packages/cli-node',
+471 -266
View File
File diff suppressed because it is too large Load Diff