backend -> backend-legacy, backend-next -> backend

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-05-01 15:06:45 +02:00
parent 4eca697bf3
commit f4fcce8cdc
50 changed files with 7791 additions and 7745 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ Cloud Console and within a Backstage app required to enable this capability.
Apps using the [new backend system](../../backend-system/index.md),
can enable the VMware Cloud provider with a small modification like:
```ts title="packages/backend-next/src/index.ts"
```ts title="packages/backend/src/index.ts"
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
+1 -1
View File
@@ -10,4 +10,4 @@ description: The Backend System
The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system.
You can find an example backend setup in [the `backend-next` package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
You can find an example backend setup in [the `backend` package](https://github.com/backstage/backstage/tree/master/packages/backend).
+1 -1
View File
@@ -145,7 +145,7 @@ from `@backstage/core-plugin-api`.
In the old backend system plugins, the configuration is passed in via options from the main
backend package. See for example
[packages/backend/src/plugins/auth.ts](https://github.com/backstage/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/backend/src/plugins/auth.ts#L23).
[packages/backend-legacy/src/plugins/auth.ts](https://github.com/backstage/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/backend/src/plugins/auth.ts#L23).
### New Backend System
-2
View File
@@ -64,8 +64,6 @@ This feature assumes your backstage instance has enabled the [permissions framew
A sample policy like:
[packages/backend/src/plugins/permissions.ts](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/permission.ts)
```typescript
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import {
+1 -1
View File
@@ -393,7 +393,7 @@ There are other more specific search results layout components that also accept
Recently, the Backstage maintainers [announced the new Backend System](https://backstage.io/blog/2023/02/15/backend-system-alpha). The search plugins are now migrated to support the new backend system. In this guide you will learn how to update your backend set up.
In "packages/backend-next/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]:
In "packages/backend/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]:
```ts
import { searchPlugin } from '@backstage/plugin-search-backend/alpha';
@@ -55,7 +55,7 @@ Some defining traits of entity providers:
The recommended way of instantiating the catalog backend classes is to use the
`CatalogBuilder`, as illustrated in the
[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/catalog.ts).
[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend-legacy/src/plugins/catalog.ts).
We will create a new
[`EntityProvider`](https://github.com/backstage/backstage/blob/master/plugins/catalog-node/src/api/provider.ts)
subclass that can be added to this catalog builder.
@@ -531,7 +531,7 @@ does so!
The recommended way of instantiating the catalog backend classes is to use the
`CatalogBuilder`, as illustrated in the
[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/catalog.ts).
[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend-legacy/src/plugins/catalog.ts).
We will create a new
[`CatalogProcessor`](https://github.com/backstage/backstage/blob/master/plugins/catalog-node/src/api/processor.ts)
subclass that can be added to this catalog builder.
+1 -1
View File
@@ -8,7 +8,7 @@ description: Details of the new backend system
The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system.
You can find an example backend setup in [the backend-next package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
You can find an example backend setup in [the backend package](https://github.com/backstage/backstage/tree/master/packages/backend).
## Overview
+3 -3
View File
@@ -24,7 +24,7 @@
"clean": "backstage-cli repo clean",
"create-plugin": "echo \"use 'yarn new' instead\"",
"dev": "yarn workspaces foreach -A --include example-backend --include example-app --parallel -v -i run start",
"dev:next": "yarn workspaces foreach -A --include example-backend-next --include example-app-next --parallel -v -i run start",
"dev:next": "yarn workspaces foreach -A --include example-backend --include example-app-next --parallel -v -i run start",
"docker-build": "yarn tsc && yarn workspace example-backend build && yarn workspace example-backend build-image",
"fix": "backstage-cli repo fix",
"postinstall": "husky || true",
@@ -41,8 +41,8 @@
"snyk:test:package": "yarn snyk:test --include",
"start": "yarn workspace example-app start",
"start-backend": "yarn workspace example-backend start",
"start-backend:next": "yarn workspace example-backend-next start",
"start:lighthouse": "yarn workspaces foreach -A --include example-backend-next --include example-app --parallel -v -i run start",
"start-backend:legacy": "yarn workspace example-backend-legacy start",
"start:lighthouse": "yarn workspaces foreach -A --include example-backend --include example-app --parallel -v -i run start",
"start:microsite": "cd microsite/ && yarn start",
"start:next": "yarn workspace example-app-next start",
"storybook": "yarn --cwd storybook && yarn --cwd storybook start",
+1 -1
View File
@@ -4,4 +4,4 @@ This package is an example of a Backstage application using the [new frontend](.
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 or [backend-next](../../docs/backend-system/index.md) there, using `yarn start`! The frontend requires a backend to connect to.
**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 @@ In order to test the dynamic backend plugins feature provided by this package, e
The dynamic plugin manager is a service that scans a configured root directory (`dynamicPlugins.rootDirectory` in the app config) for dynamic plugin packages, and loads them dynamically.
In the `backend-next` application, it can be enabled by adding the `backend-dynamic-feature-service` as a dependency in the `package.json` and the following lines in the `src/index.ts` file:
In the `backend` application, it can be enabled by adding the `backend-dynamic-feature-service` as a dependency in the `package.json` and the following lines in the `src/index.ts` file:
```ts
const backend = createBackend();
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
# example-backend-legacy
This package is an EXAMPLE of a Backstage backend using the old backend system.
The main purpose of this package is to provide a test bed for Backstage plugins
that have a backend part. Feel free to experiment locally or within your fork
by adding dependencies and routes to this backend, to try things out.
By running the `@backstage/create-app` script, you get your own separate Backstage backend.
## Development
To run the example backend, first go to the project root and run
```bash
yarn install
```
You should only need to do this once.
After that, go to the `packages/backend-legacy` directory and run
```bash
yarn start
```
If you want to override any configuration locally, for example adding any secrets,
you can do so in `app-config.local.yaml`.
The backend starts up on port 7007 per default.
### Debugging
The backend is a node process that can be inspected to allow breakpoints and live debugging. To enable this, pass the `--inspect` flag to [backend:dev](https://backstage.io/docs/local-dev/cli-build-system#backend-development).
To debug the backend in [Visual Studio Code](https://code.visualstudio.com/):
- Enable Auto Attach (⌘ + Shift + P > Toggle Auto Attach > Only With Flag)
- Open a VSCode terminal (Control + `)
- Run the backend from the VSCode terminal: `yarn start-backend:legacy --inspect`
## Populating The Catalog
If you want to use the catalog functionality, you need to add so called
locations to the backend. These are places where the backend can find some
entity descriptor data to consume and serve. For more information, see
[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog).
For convenience we already include some statically configured example locations
in `app-config.yaml` under `catalog.locations`. For local development you can override these in your own `app-config.local.yaml`.
## Authentication
We chose [Passport](http://www.passportjs.org/) as authentication platform due to its comprehensive set of supported authentication [strategies](http://www.passportjs.org/packages/).
Read more about the [auth-backend](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md) and [how to add a new provider](https://github.com/backstage/backstage/blob/master/docs/auth/add-auth-provider.md)
## Documentation
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
- [Backstage Documentation](https://backstage.io/docs)
@@ -1,8 +1,8 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: example-backend-next
title: example-backend-next
name: example-backend-legacy
title: example-backend-legacy
spec:
lifecycle: experimental
type: backstage-backend
+27
View File
@@ -0,0 +1,27 @@
# Knip report
## Unused dependencies (13)
| Name | Location | Severity |
| :------------------------------------------------- | :----------- | :------- |
| @backstage/plugin-scaffolder-backend-module-gitlab | package.json | error |
| @backstage/plugin-scaffolder-backend-module-rails | package.json | error |
| @backstage/plugin-azure-sites-common | package.json | error |
| @backstage/plugin-tech-insights-node | package.json | error |
| azure-devops-node-api | package.json | error |
| pg-connection-string | package.json | error |
| @gitbeaker/node | package.json | error |
| better-sqlite3 | package.json | error |
| @octokit/rest | package.json | error |
| example-app | package.json | error |
| mysql2 | package.json | error |
| luxon | package.json | error |
| pg | package.json | error |
## Unused devDependencies (2)
| Name | Location | Severity |
| :------------------------------- | :----------- | :------- |
| @types/express-serve-static-core | package.json | error |
| @types/luxon | package.json | error |
@@ -1,21 +1,24 @@
{
"name": "example-backend-next",
"version": "0.0.26-next.1",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"name": "example-backend-legacy",
"version": "0.2.98-next.1",
"backstage": {
"role": "backend"
},
"private": true,
"keywords": [
"backstage"
],
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/backend-next"
"directory": "packages/backend-legacy"
},
"keywords": [
"backstage"
"license": "Apache-2.0",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "backstage-cli package build",
@@ -25,42 +28,65 @@
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-defaults": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-common": "workspace:^",
"@backstage/backend-tasks": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/plugin-app-backend": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^",
"@backstage/plugin-auth-backend-module-guest-provider": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/plugin-catalog-backend": "workspace:^",
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^",
"@backstage/plugin-catalog-backend-module-openapi": "workspace:^",
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^",
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/plugin-devtools-backend": "workspace:^",
"@backstage/plugin-events-backend": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
"@backstage/plugin-kubernetes-backend": "workspace:^",
"@backstage/plugin-notifications-backend": "workspace:^",
"@backstage/plugin-permission-backend": "workspace:^",
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
"@backstage/plugin-proxy-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-github": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-rails": "workspace:^",
"@backstage/plugin-search-backend": "workspace:^",
"@backstage/plugin-search-backend-module-catalog": "workspace:^",
"@backstage/plugin-search-backend-module-elasticsearch": "workspace:^",
"@backstage/plugin-search-backend-module-explore": "workspace:^",
"@backstage/plugin-search-backend-module-pg": "workspace:^",
"@backstage/plugin-search-backend-module-techdocs": "workspace:^",
"@backstage/plugin-search-backend-node": "workspace:^",
"@backstage/plugin-signals-backend": "workspace:^",
"@backstage/plugin-techdocs-backend": "workspace:^"
"@backstage/plugin-signals-node": "workspace:^",
"@backstage/plugin-techdocs-backend": "workspace:^",
"@gitbeaker/node": "^35.1.0",
"@octokit/rest": "^19.0.3",
"@opentelemetry/api": "^1.4.1",
"@opentelemetry/exporter-prometheus": "^0.50.0",
"@opentelemetry/sdk-metrics": "^1.13.0",
"azure-devops-node-api": "^12.0.0",
"better-sqlite3": "^9.0.0",
"dockerode": "^4.0.0",
"example-app": "link:../app",
"express": "^4.17.1",
"express-prom-bundle": "^7.0.0",
"express-promise-router": "^4.1.0",
"luxon": "^3.0.0",
"mysql2": "^3.0.0",
"pg": "^8.11.3",
"pg-connection-string": "^2.3.0",
"prom-client": "^15.0.0",
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
},
"files": [
"dist"
]
"@backstage/cli": "workspace:^",
"@types/dockerode": "^3.3.0",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
"@types/luxon": "^3.0.0"
}
}
+184
View File
@@ -0,0 +1,184 @@
/*
* Copyright 2022 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.
*/
/*
* Hi!
*
* Note that this is an EXAMPLE Backstage backend. Please check the README.
*
* Happy hacking!
*/
import Router from 'express-promise-router';
import {
CacheManager,
createServiceBuilder,
DatabaseManager,
getRootLogger,
HostDiscovery,
loadBackendConfig,
notFoundHandler,
ServerTokenManager,
UrlReaders,
useHotMemoize,
} from '@backstage/backend-common';
import { TaskScheduler } from '@backstage/backend-tasks';
import { Config } from '@backstage/config';
import healthcheck from './plugins/healthcheck';
import { metricsHandler, metricsInit } from './metrics';
import auth from './plugins/auth';
import catalog from './plugins/catalog';
import events from './plugins/events';
import kubernetes from './plugins/kubernetes';
import scaffolder from './plugins/scaffolder';
import proxy from './plugins/proxy';
import search from './plugins/search';
import techdocs from './plugins/techdocs';
import app from './plugins/app';
import permission from './plugins/permission';
import signals from './plugins/signals';
import devtools from './plugins/devtools';
import { PluginEnvironment } from './types';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { DefaultEventBroker } from '@backstage/plugin-events-backend';
import { DefaultEventsService } from '@backstage/plugin-events-node';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { MeterProvider } from '@opentelemetry/sdk-metrics';
import { metrics } from '@opentelemetry/api';
import { DefaultSignalsService } from '@backstage/plugin-signals-node';
// Expose opentelemetry metrics using a Prometheus exporter on
// http://localhost:9464/metrics . See prometheus.yml in packages/backend for
// more information on how to scrape it.
const exporter = new PrometheusExporter();
const meterProvider = new MeterProvider();
metrics.setGlobalMeterProvider(meterProvider);
meterProvider.addMetricReader(exporter);
function makeCreateEnv(config: Config) {
const root = getRootLogger();
const reader = UrlReaders.default({ logger: root, config });
const discovery = HostDiscovery.fromConfig(config);
const tokenManager = ServerTokenManager.fromConfig(config, { logger: root });
const permissions = ServerPermissionClient.fromConfig(config, {
discovery,
tokenManager,
});
const databaseManager = DatabaseManager.fromConfig(config, { logger: root });
const cacheManager = CacheManager.fromConfig(config);
const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager });
const identity = DefaultIdentityClient.create({
discovery,
});
const eventsService = DefaultEventsService.create({ logger: root });
const eventBroker = new DefaultEventBroker(
root.child({ type: 'plugin' }),
eventsService,
);
const signalsService = DefaultSignalsService.create({
events: eventsService,
});
root.info(`Created UrlReader ${reader}`);
return (plugin: string): PluginEnvironment => {
const logger = root.child({ type: 'plugin', plugin });
const database = databaseManager.forPlugin(plugin);
const cache = cacheManager.forPlugin(plugin);
const scheduler = taskScheduler.forPlugin(plugin);
return {
logger,
cache,
database,
config,
reader,
eventBroker,
events: eventsService,
discovery,
tokenManager,
permissions,
scheduler,
identity,
signals: signalsService,
};
};
}
async function main() {
metricsInit();
const logger = getRootLogger();
logger.info(
`You are running an example backend, which is supposed to be mainly used for contributing back to Backstage. ` +
`Do NOT deploy this to production. Read more here https://backstage.io/docs/getting-started/`,
);
const config = await loadBackendConfig({
argv: process.argv,
logger,
});
const createEnv = makeCreateEnv(config);
const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck'));
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
const authEnv = useHotMemoize(module, () => createEnv('auth'));
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
const searchEnv = useHotMemoize(module, () => createEnv('search'));
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
const permissionEnv = useHotMemoize(module, () => createEnv('permission'));
const eventsEnv = useHotMemoize(module, () => createEnv('events'));
const devToolsEnv = useHotMemoize(module, () => createEnv('devtools'));
const signalsEnv = useHotMemoize(module, () => createEnv('signals'));
const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv));
apiRouter.use('/events', await events(eventsEnv));
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
apiRouter.use('/auth', await auth(authEnv));
apiRouter.use('/search', await search(searchEnv));
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/permission', await permission(permissionEnv));
apiRouter.use('/devtools', await devtools(devToolsEnv));
apiRouter.use('/signals', await signals(signalsEnv));
apiRouter.use(notFoundHandler());
const service = createServiceBuilder(module)
.loadConfig(config)
.addRouter('', await healthcheck(healthcheckEnv))
.addRouter('', metricsHandler())
.addRouter('/api', apiRouter)
.addRouter('', await app(appEnv));
await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
main().catch(error => {
console.error('Backend failed to start up', error);
process.exit(1);
});
File diff suppressed because it is too large Load Diff
-5
View File
@@ -1,5 +0,0 @@
# example-backend-next
This is an example backend for [the new Backstage backend system](https://backstage.io/docs/backend-system/).
Do not use this in your own projects.
-12
View File
@@ -1,12 +0,0 @@
# Knip report
## Unused dependencies (5)
| Name | Location | Severity |
| :----------------------------------------------- | :----------- | :------- |
| @backstage/plugin-catalog-backend-module-openapi | package.json | error |
| @backstage/plugin-search-backend-node | package.json | error |
| @backstage/plugin-permission-common | package.json | error |
| @backstage/plugin-permission-node | package.json | error |
| @backstage/backend-tasks | package.json | error |
-51
View File
@@ -1,51 +0,0 @@
/*
* Copyright 2022 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 { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
backend.add(import('@backstage/plugin-auth-backend'));
backend.add(import('./authModuleGithubProvider'));
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@backstage/plugin-catalog-backend-module-unprocessed'));
backend.add(
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
);
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
backend.add(import('@backstage/plugin-devtools-backend'));
backend.add(import('@backstage/plugin-kubernetes-backend/alpha'));
backend.add(
import('@backstage/plugin-permission-backend-module-allow-all-policy'),
);
backend.add(import('@backstage/plugin-permission-backend/alpha'));
backend.add(import('@backstage/plugin-proxy-backend/alpha'));
backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-explore/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
backend.add(
import('@backstage/plugin-catalog-backend-module-backstage-openapi'),
);
backend.add(import('@backstage/plugin-search-backend/alpha'));
backend.add(import('@backstage/plugin-techdocs-backend/alpha'));
backend.add(import('@backstage/plugin-signals-backend'));
backend.add(import('@backstage/plugin-notifications-backend'));
backend.start();
File diff suppressed because it is too large Load Diff
+7 -9
View File
@@ -1,6 +1,6 @@
# example-backend
This package is an EXAMPLE of a Backstage backend.
This package is an EXAMPLE of a Backstage backend using the [new backend system](https://backstage.io/docs/backend-system/).
The main purpose of this package is to provide a test bed for Backstage plugins
that have a backend part. Feel free to experiment locally or within your fork
@@ -16,22 +16,22 @@ To run the example backend, first go to the project root and run
yarn install
```
You should only need to do this once.
This will install all dependencies for the project. You only need to do this once, unless you make changes to the dependency definitions.
After that, go to the `packages/backend` directory and run
You can then start the backend by running the following command in the repo root:
```bash
yarn start
yarn start-backend
```
If you want to override any configuration locally, for example adding any secrets,
you can do so in `app-config.local.yaml`.
you can do so in `app-config.local.yaml`, next to `app-config.yaml`.
The backend starts up on port 7007 per default.
### Debugging
The backend is a node process that can be inspected to allow breakpoints and live debugging. To enable this, pass the `--inspect` flag to [backend:dev](https://backstage.io/docs/local-dev/cli-build-system#backend-development).
The backend is a node process that can be inspected to allow breakpoints and live debugging. To enable this, pass the `--inspect` flag when starting the backend.
To debug the backend in [Visual Studio Code](https://code.visualstudio.com/):
@@ -51,9 +51,7 @@ in `app-config.yaml` under `catalog.locations`. For local development you can ov
## Authentication
We chose [Passport](http://www.passportjs.org/) as authentication platform due to its comprehensive set of supported authentication [strategies](http://www.passportjs.org/packages/).
Read more about the [auth-backend](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md) and [how to add a new provider](https://github.com/backstage/backstage/blob/master/docs/auth/add-auth-provider.md)
The example backend has guest access enabled by default. This means you do not need to configure a real authentication provider, but will instead be logged in as a guest user.
## Documentation
+8 -23
View File
@@ -1,27 +1,12 @@
# Knip report
## Unused dependencies (13)
## Unused dependencies (5)
| Name | Location | Severity |
| :------------------------------------------------- | :----------- | :------- |
| @backstage/plugin-scaffolder-backend-module-gitlab | package.json | error |
| @backstage/plugin-scaffolder-backend-module-rails | package.json | error |
| @backstage/plugin-azure-sites-common | package.json | error |
| @backstage/plugin-tech-insights-node | package.json | error |
| azure-devops-node-api | package.json | error |
| pg-connection-string | package.json | error |
| @gitbeaker/node | package.json | error |
| better-sqlite3 | package.json | error |
| @octokit/rest | package.json | error |
| example-app | package.json | error |
| mysql2 | package.json | error |
| luxon | package.json | error |
| pg | package.json | error |
## Unused devDependencies (2)
| Name | Location | Severity |
| :------------------------------- | :----------- | :------- |
| @types/express-serve-static-core | package.json | error |
| @types/luxon | package.json | error |
| Name | Location | Severity |
| :----------------------------------------------- | :----------- | :------- |
| @backstage/plugin-catalog-backend-module-openapi | package.json | error |
| @backstage/plugin-search-backend-node | package.json | error |
| @backstage/plugin-permission-common | package.json | error |
| @backstage/plugin-permission-node | package.json | error |
| @backstage/backend-tasks | package.json | error |
+15 -42
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.2.98-next.1",
"version": "0.0.26-next.1",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -18,75 +18,48 @@
"backstage"
],
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"start": "backstage-cli package start",
"test": "backstage-cli package test",
"build-image": "docker build ../.. -f Dockerfile --tag example-backend"
},
"dependencies": {
"@backstage/backend-common": "workspace:^",
"@backstage/backend-defaults": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-tasks": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/plugin-app-backend": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^",
"@backstage/plugin-auth-backend-module-guest-provider": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/plugin-catalog-backend": "workspace:^",
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^",
"@backstage/plugin-catalog-backend-module-openapi": "workspace:^",
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^",
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/plugin-devtools-backend": "workspace:^",
"@backstage/plugin-events-backend": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
"@backstage/plugin-kubernetes-backend": "workspace:^",
"@backstage/plugin-notifications-backend": "workspace:^",
"@backstage/plugin-permission-backend": "workspace:^",
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
"@backstage/plugin-proxy-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-notifications": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-rails": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-github": "workspace:^",
"@backstage/plugin-search-backend": "workspace:^",
"@backstage/plugin-search-backend-module-catalog": "workspace:^",
"@backstage/plugin-search-backend-module-elasticsearch": "workspace:^",
"@backstage/plugin-search-backend-module-explore": "workspace:^",
"@backstage/plugin-search-backend-module-pg": "workspace:^",
"@backstage/plugin-search-backend-module-techdocs": "workspace:^",
"@backstage/plugin-search-backend-node": "workspace:^",
"@backstage/plugin-signals-backend": "workspace:^",
"@backstage/plugin-signals-node": "workspace:^",
"@backstage/plugin-techdocs-backend": "workspace:^",
"@gitbeaker/node": "^35.1.0",
"@octokit/rest": "^19.0.3",
"@opentelemetry/api": "^1.4.1",
"@opentelemetry/exporter-prometheus": "^0.50.0",
"@opentelemetry/sdk-metrics": "^1.13.0",
"azure-devops-node-api": "^12.0.0",
"better-sqlite3": "^9.0.0",
"dockerode": "^4.0.0",
"example-app": "link:../app",
"express": "^4.17.1",
"express-prom-bundle": "^7.0.0",
"express-promise-router": "^4.1.0",
"luxon": "^3.0.0",
"mysql2": "^3.0.0",
"pg": "^8.11.3",
"pg-connection-string": "^2.3.0",
"prom-client": "^15.0.0",
"winston": "^3.2.1"
"@backstage/plugin-techdocs-backend": "workspace:^"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@types/dockerode": "^3.3.0",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
"@types/luxon": "^3.0.0"
"@backstage/cli": "workspace:^"
},
"files": [
"dist"
+31 -164
View File
@@ -14,171 +14,38 @@
* limitations under the License.
*/
/*
* Hi!
*
* Note that this is an EXAMPLE Backstage backend. Please check the README.
*
* Happy hacking!
*/
import { createBackend } from '@backstage/backend-defaults';
import Router from 'express-promise-router';
import {
CacheManager,
createServiceBuilder,
DatabaseManager,
getRootLogger,
HostDiscovery,
loadBackendConfig,
notFoundHandler,
ServerTokenManager,
UrlReaders,
useHotMemoize,
} from '@backstage/backend-common';
import { TaskScheduler } from '@backstage/backend-tasks';
import { Config } from '@backstage/config';
import healthcheck from './plugins/healthcheck';
import { metricsHandler, metricsInit } from './metrics';
import auth from './plugins/auth';
import catalog from './plugins/catalog';
import events from './plugins/events';
import kubernetes from './plugins/kubernetes';
import scaffolder from './plugins/scaffolder';
import proxy from './plugins/proxy';
import search from './plugins/search';
import techdocs from './plugins/techdocs';
import app from './plugins/app';
import permission from './plugins/permission';
import signals from './plugins/signals';
import devtools from './plugins/devtools';
import { PluginEnvironment } from './types';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { DefaultEventBroker } from '@backstage/plugin-events-backend';
import { DefaultEventsService } from '@backstage/plugin-events-node';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { MeterProvider } from '@opentelemetry/sdk-metrics';
import { metrics } from '@opentelemetry/api';
import { DefaultSignalsService } from '@backstage/plugin-signals-node';
const backend = createBackend();
// Expose opentelemetry metrics using a Prometheus exporter on
// http://localhost:9464/metrics . See prometheus.yml in packages/backend for
// more information on how to scrape it.
const exporter = new PrometheusExporter();
const meterProvider = new MeterProvider();
metrics.setGlobalMeterProvider(meterProvider);
meterProvider.addMetricReader(exporter);
backend.add(import('@backstage/plugin-auth-backend'));
backend.add(import('./authModuleGithubProvider'));
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
function makeCreateEnv(config: Config) {
const root = getRootLogger();
const reader = UrlReaders.default({ logger: root, config });
const discovery = HostDiscovery.fromConfig(config);
const tokenManager = ServerTokenManager.fromConfig(config, { logger: root });
const permissions = ServerPermissionClient.fromConfig(config, {
discovery,
tokenManager,
});
const databaseManager = DatabaseManager.fromConfig(config, { logger: root });
const cacheManager = CacheManager.fromConfig(config);
const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager });
const identity = DefaultIdentityClient.create({
discovery,
});
backend.add(import('@backstage/plugin-app-backend/alpha'));
backend.add(import('@backstage/plugin-catalog-backend-module-unprocessed'));
backend.add(
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
);
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
backend.add(import('@backstage/plugin-devtools-backend'));
backend.add(import('@backstage/plugin-kubernetes-backend/alpha'));
backend.add(
import('@backstage/plugin-permission-backend-module-allow-all-policy'),
);
backend.add(import('@backstage/plugin-permission-backend/alpha'));
backend.add(import('@backstage/plugin-proxy-backend/alpha'));
backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-explore/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
backend.add(
import('@backstage/plugin-catalog-backend-module-backstage-openapi'),
);
backend.add(import('@backstage/plugin-search-backend/alpha'));
backend.add(import('@backstage/plugin-techdocs-backend/alpha'));
backend.add(import('@backstage/plugin-signals-backend'));
backend.add(import('@backstage/plugin-notifications-backend'));
const eventsService = DefaultEventsService.create({ logger: root });
const eventBroker = new DefaultEventBroker(
root.child({ type: 'plugin' }),
eventsService,
);
const signalsService = DefaultSignalsService.create({
events: eventsService,
});
root.info(`Created UrlReader ${reader}`);
return (plugin: string): PluginEnvironment => {
const logger = root.child({ type: 'plugin', plugin });
const database = databaseManager.forPlugin(plugin);
const cache = cacheManager.forPlugin(plugin);
const scheduler = taskScheduler.forPlugin(plugin);
return {
logger,
cache,
database,
config,
reader,
eventBroker,
events: eventsService,
discovery,
tokenManager,
permissions,
scheduler,
identity,
signals: signalsService,
};
};
}
async function main() {
metricsInit();
const logger = getRootLogger();
logger.info(
`You are running an example backend, which is supposed to be mainly used for contributing back to Backstage. ` +
`Do NOT deploy this to production. Read more here https://backstage.io/docs/getting-started/`,
);
const config = await loadBackendConfig({
argv: process.argv,
logger,
});
const createEnv = makeCreateEnv(config);
const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck'));
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
const authEnv = useHotMemoize(module, () => createEnv('auth'));
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
const searchEnv = useHotMemoize(module, () => createEnv('search'));
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
const permissionEnv = useHotMemoize(module, () => createEnv('permission'));
const eventsEnv = useHotMemoize(module, () => createEnv('events'));
const devToolsEnv = useHotMemoize(module, () => createEnv('devtools'));
const signalsEnv = useHotMemoize(module, () => createEnv('signals'));
const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv));
apiRouter.use('/events', await events(eventsEnv));
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
apiRouter.use('/auth', await auth(authEnv));
apiRouter.use('/search', await search(searchEnv));
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/permission', await permission(permissionEnv));
apiRouter.use('/devtools', await devtools(devToolsEnv));
apiRouter.use('/signals', await signals(signalsEnv));
apiRouter.use(notFoundHandler());
const service = createServiceBuilder(module)
.loadConfig(config)
.addRouter('', await healthcheck(healthcheckEnv))
.addRouter('', metricsHandler())
.addRouter('/api', apiRouter)
.addRouter('', await app(appEnv));
await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
main().catch(error => {
console.error('Backend failed to start up', error);
process.exit(1);
});
backend.start();
+2 -2
View File
@@ -14,7 +14,7 @@ import { AwsAlbResult as AwsAlbResult_2 } from '@backstage/plugin-auth-backend-m
import { AzureEasyAuthResult } from '@backstage/plugin-auth-backend-module-azure-easyauth-provider';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { BackstageSignInResult } from '@backstage/plugin-auth-node';
import { CacheService } from '@backstage/backend-plugin-api';
import { CacheClient } from '@backstage/backend-common';
import { CatalogApi } from '@backstage/catalog-client';
import { ClientAuthResponse } from '@backstage/plugin-auth-node';
import { cloudflareAccessSignInResolvers } from '@backstage/plugin-auth-backend-module-cloudflare-access-provider';
@@ -452,7 +452,7 @@ export const providers: Readonly<{
signIn: {
resolver: SignInResolver_2<CloudflareAccessResult>;
};
cache?: CacheService | undefined;
cache?: CacheClient | undefined;
}) => AuthProviderFactory_2;
resolvers: Readonly<cloudflareAccessSignInResolvers>;
}>;
@@ -14,7 +14,13 @@ A `pending` entity has not been processed yet.
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-unprocessed
```
### backend
In `packages/backend/src/index.ts` add the module:
```ts title="packages/backend/src/index.ts"
backend.add(catalogModuleUnprocessedEntities());
```
### Legacy Backend
In `packages/backend/src/plugins/catalog.ts` import the module and initialize it after invoking `CatalogBuilder.build()`:
@@ -29,13 +35,3 @@ const unprocessed = new UnprocessedEntitiesModule(
);
unprocessed.registerRoutes();
```
### backend-next
In `packages/backend-next/src/index.ts` add the module:
```ts title="packages/backend-next/src/index.ts"
backend.add(catalogModuleUnprocessedEntities());
```
_This plugin was created through the Backstage CLI_
@@ -14,7 +14,7 @@ yarn --cwd packages/backend add @backstage/plugin-search-backend-module-catalog
Add the collator to your backend instance, along with the search plugin itself:
```tsx
// packages/backend-next/src/index.ts
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { searchPlugin } from '@backstage/plugin-search-backend/alpha';
import { searchModuleCatalogCollator } from '@backstage/plugin-search-backend-module-catalog/alpha';
@@ -32,7 +32,7 @@ You may also want to add configuration parameters to your app-config, for exampl
This module also has an extension point, which lets you inject advanced customizations. Here's an example of how to leverage that extension point to tweak the transformer used for building the search indexer documents:
```tsx
// packages/backend-next/src/index.ts
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { createBackendModule } from '@backstage/backend-plugin-api';
import { searchPlugin } from '@backstage/plugin-search-backend/alpha';
+44 -45
View File
@@ -6695,7 +6695,7 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/plugin-scaffolder-backend-module-notifications@workspace:^, @backstage/plugin-scaffolder-backend-module-notifications@workspace:plugins/scaffolder-backend-module-notifications":
"@backstage/plugin-scaffolder-backend-module-notifications@workspace:plugins/scaffolder-backend-module-notifications":
version: 0.0.0-use.local
resolution: "@backstage/plugin-scaffolder-backend-module-notifications@workspace:plugins/scaffolder-backend-module-notifications"
dependencies:
@@ -24107,9 +24107,9 @@ __metadata:
languageName: unknown
linkType: soft
"example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend":
"example-app@link:../app::locator=example-backend-legacy%40workspace%3Apackages%2Fbackend-legacy":
version: 0.0.0-use.local
resolution: "example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend"
resolution: "example-app@link:../app::locator=example-backend-legacy%40workspace%3Apackages%2Fbackend-legacy"
languageName: node
linkType: soft
@@ -24181,48 +24181,9 @@ __metadata:
languageName: unknown
linkType: soft
"example-backend-next@workspace:packages/backend-next":
"example-backend-legacy@workspace:packages/backend-legacy":
version: 0.0.0-use.local
resolution: "example-backend-next@workspace:packages/backend-next"
dependencies:
"@backstage/backend-defaults": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-tasks": "workspace:^"
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/plugin-app-backend": "workspace:^"
"@backstage/plugin-auth-backend": "workspace:^"
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^"
"@backstage/plugin-auth-backend-module-guest-provider": "workspace:^"
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/plugin-catalog-backend": "workspace:^"
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^"
"@backstage/plugin-catalog-backend-module-openapi": "workspace:^"
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^"
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^"
"@backstage/plugin-devtools-backend": "workspace:^"
"@backstage/plugin-kubernetes-backend": "workspace:^"
"@backstage/plugin-notifications-backend": "workspace:^"
"@backstage/plugin-permission-backend": "workspace:^"
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^"
"@backstage/plugin-permission-common": "workspace:^"
"@backstage/plugin-permission-node": "workspace:^"
"@backstage/plugin-proxy-backend": "workspace:^"
"@backstage/plugin-scaffolder-backend": "workspace:^"
"@backstage/plugin-scaffolder-backend-module-github": "workspace:^"
"@backstage/plugin-search-backend": "workspace:^"
"@backstage/plugin-search-backend-module-catalog": "workspace:^"
"@backstage/plugin-search-backend-module-explore": "workspace:^"
"@backstage/plugin-search-backend-module-techdocs": "workspace:^"
"@backstage/plugin-search-backend-node": "workspace:^"
"@backstage/plugin-signals-backend": "workspace:^"
"@backstage/plugin-techdocs-backend": "workspace:^"
languageName: unknown
linkType: soft
"example-backend@workspace:packages/backend":
version: 0.0.0-use.local
resolution: "example-backend@workspace:packages/backend"
resolution: "example-backend-legacy@workspace:packages/backend-legacy"
dependencies:
"@backstage/backend-common": "workspace:^"
"@backstage/backend-tasks": "workspace:^"
@@ -24249,7 +24210,6 @@ __metadata:
"@backstage/plugin-scaffolder-backend": "workspace:^"
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^"
"@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^"
"@backstage/plugin-scaffolder-backend-module-notifications": "workspace:^"
"@backstage/plugin-scaffolder-backend-module-rails": "workspace:^"
"@backstage/plugin-search-backend": "workspace:^"
"@backstage/plugin-search-backend-module-catalog": "workspace:^"
@@ -24286,6 +24246,45 @@ __metadata:
languageName: unknown
linkType: soft
"example-backend@workspace:packages/backend":
version: 0.0.0-use.local
resolution: "example-backend@workspace:packages/backend"
dependencies:
"@backstage/backend-defaults": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-tasks": "workspace:^"
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/plugin-app-backend": "workspace:^"
"@backstage/plugin-auth-backend": "workspace:^"
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^"
"@backstage/plugin-auth-backend-module-guest-provider": "workspace:^"
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/plugin-catalog-backend": "workspace:^"
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^"
"@backstage/plugin-catalog-backend-module-openapi": "workspace:^"
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^"
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^"
"@backstage/plugin-devtools-backend": "workspace:^"
"@backstage/plugin-kubernetes-backend": "workspace:^"
"@backstage/plugin-notifications-backend": "workspace:^"
"@backstage/plugin-permission-backend": "workspace:^"
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^"
"@backstage/plugin-permission-common": "workspace:^"
"@backstage/plugin-permission-node": "workspace:^"
"@backstage/plugin-proxy-backend": "workspace:^"
"@backstage/plugin-scaffolder-backend": "workspace:^"
"@backstage/plugin-scaffolder-backend-module-github": "workspace:^"
"@backstage/plugin-search-backend": "workspace:^"
"@backstage/plugin-search-backend-module-catalog": "workspace:^"
"@backstage/plugin-search-backend-module-explore": "workspace:^"
"@backstage/plugin-search-backend-module-techdocs": "workspace:^"
"@backstage/plugin-search-backend-node": "workspace:^"
"@backstage/plugin-signals-backend": "workspace:^"
"@backstage/plugin-techdocs-backend": "workspace:^"
languageName: unknown
linkType: soft
"execa@npm:8.0.1":
version: 8.0.1
resolution: "execa@npm:8.0.1"