Merge remote-tracking branch 'origin/master' into task-action-idempotency

This commit is contained in:
bnechyporenko
2024-02-10 14:10:36 +01:00
414 changed files with 5352 additions and 3812 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Added check for the `repository` field in the `repo fix` command.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': patch
---
Introduced `knip` to the `knip-reports` command, which generates a `knip-report.md` file for your packages with dependency warnings, if any.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli-node': patch
---
Added `repository` field to `BackstagePackageJson` type.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
adds a x-goog-api-client header to existing API requests in this plugin to clearly identify API requests from this GKE plugin. headers are formatted as follows where `libVersion` represents the current dotted version number of the Backstage GKE plugin and `libName` represent the current Google API used at backstage.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-playlist': patch
---
Conditionally render playlist description only when it's non-empty
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Organized imports in plugin.ts
+46
View File
@@ -0,0 +1,46 @@
---
'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': patch
'@backstage/plugin-search-backend-module-stack-overflow-collator': patch
'@backstage/plugin-permission-backend-module-allow-all-policy': patch
'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch
'@backstage/plugin-auth-backend-module-oauth2-proxy-provider': patch
'@backstage/plugin-auth-backend-module-vmware-cloud-provider': patch
'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch
'@backstage/plugin-catalog-backend-module-backstage-openapi': patch
'@backstage/plugin-auth-backend-module-atlassian-provider': patch
'@backstage/plugin-auth-backend-module-microsoft-provider': patch
'@backstage/plugin-auth-backend-module-pinniped-provider': patch
'@backstage/plugin-auth-backend-module-github-provider': patch
'@backstage/plugin-auth-backend-module-gitlab-provider': patch
'@backstage/plugin-auth-backend-module-oauth2-provider': patch
'@backstage/plugin-scaffolder-backend-module-bitbucket': patch
'@backstage/plugin-analytics-module-newrelic-browser': patch
'@backstage/plugin-auth-backend-module-oidc-provider': patch
'@backstage/plugin-auth-backend-module-okta-provider': patch
'@backstage/plugin-catalog-backend-module-github-org': patch
'@backstage/backend-dynamic-feature-service': patch
'@backstage/plugin-scaffolder-backend-module-gerrit': patch
'@backstage/plugin-scaffolder-backend-module-github': patch
'@backstage/plugin-scaffolder-backend-module-azure': patch
'@backstage/plugin-notifications-backend': patch
'@backstage/frontend-test-utils': patch
'@backstage/plugin-analytics-module-ga4': patch
'@backstage/plugin-notifications-common': patch
'@backstage/plugin-notifications-node': patch
'@backstage/frontend-app-api': patch
'@backstage/plugin-kubernetes-react': patch
'@backstage/e2e-test-utils': patch
'@backstage/plugin-kubernetes-node': patch
'@backstage/plugin-signals-backend': patch
'@backstage/plugin-app-visualizer': patch
'@backstage/plugin-notifications': patch
'@backstage/plugin-signals-react': patch
'@backstage/plugin-vault-backend': patch
'@backstage/plugin-signals-node': patch
'@backstage/plugin-vault-node': patch
'@backstage/plugin-app-node': patch
'@backstage/plugin-opencost': patch
'@backstage/plugin-signals': patch
---
Added or fixed the `repository` field in `package.json`.
+11
View File
@@ -0,0 +1,11 @@
---
'@backstage/core-components': patch
'@backstage/plugin-catalog-react': patch
'@backstage/plugin-entity-validation': patch
'@backstage/plugin-gcp-projects': patch
'@backstage/plugin-scaffolder-react': patch
'@backstage/plugin-scaffolder': patch
'@backstage/plugin-techdocs-module-addons-contrib': patch
---
Updated dependency `@react-hookz/web` to `^24.0.0`.
+11
View File
@@ -0,0 +1,11 @@
---
'@backstage/plugin-home-react': patch
'@backstage/plugin-home': patch
'@backstage/plugin-scaffolder-react': patch
'@backstage/plugin-scaffolder': patch
---
Updated dependency `@rjsf/utils` to `5.17.0`.
Updated dependency `@rjsf/core` to `5.17.0`.
Updated dependency `@rjsf/material-ui` to `5.17.0`.
Updated dependency `@rjsf/validator-ajv8` to `5.17.0`.
+8
View File
@@ -0,0 +1,8 @@
---
'@backstage/plugin-gcalendar': patch
'@backstage/plugin-microsoft-calendar': patch
'@backstage/plugin-techdocs': patch
---
Updated dependency `dompurify` to `^3.0.0`.
Updated dependency `@types/dompurify` to `^3.0.0`.
+15
View File
@@ -0,0 +1,15 @@
---
'@backstage/plugin-catalog-backend': patch
'@backstage/plugin-catalog-node': patch
---
Add support for `onProcessingError` handler at the catalog plugin (new backend system).
You can use `setOnProcessingErrorHandler` at the `catalogProcessingExtensionPoint`
as replacement for
```ts
catalogBuilder.subscribe({
onProcessingError: hander,
});
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
On LocalKubectlProxyClusterLocator, when resolving localhost, IPv4 address is placed before IPv6 address, ignoring the order from the DNS resolver. This change is necessary since by default kubectl proxy listen on IPv4
+2
View File
@@ -86,3 +86,5 @@ yarn.lock @backstage/maintainers @backst
/plugins/tech-insights-backend-module-jsonfc @backstage/maintainers @backstage/reviewers @xantier
/plugins/tech-insights-tech-insights-common @backstage/maintainers @backstage/reviewers @xantier
/plugins/tech-insights-tech-insights-node @backstage/maintainers @backstage/reviewers @xantier
knip-report.md
@@ -186,6 +186,7 @@ Kaswell
keepalive
Keyv
Knex
knip
Koyeb
KPIs
Kuang
+3 -3
View File
@@ -155,7 +155,7 @@ jobs:
name: Test ${{ matrix.node-version }}
services:
postgres16:
image: postgres:16@sha256:4d1b17af6f66b852ee3a721f6691a2ca7352f9d28f570a6a48cee4ebe646b2fd
image: postgres:16@sha256:09f23e02d76670d3b346a3c00aa33a27cf57aab8341eedfcdaed41459d14f5c4
env:
POSTGRES_PASSWORD: postgres
options: >-
@@ -166,7 +166,7 @@ jobs:
ports:
- 5432/tcp
postgres12:
image: postgres:12@sha256:aafc7d3faafa5f95fa4709007c742864747a104c3d950f98bb606145208e4a77
image: postgres:12@sha256:8ffdca6e9208aceb30653fa0cc1c38956303ace453944b54b4f71019cfe854c1
env:
POSTGRES_PASSWORD: postgres
options: >-
@@ -177,7 +177,7 @@ jobs:
ports:
- 5432/tcp
mysql8:
image: mysql:8@sha256:d7c20c5ba268c558f4fac62977f8c7125bde0630ff8946b08dde44135ef40df3
image: mysql:8@sha256:2a9ef1075ff30c65bbcf4f96b25a03ea3b3f492c284e6c4a612c269ce4c5bb19
env:
MYSQL_ROOT_PASSWORD: root
options: >-
+3 -3
View File
@@ -23,7 +23,7 @@ jobs:
services:
postgres16:
image: postgres:16@sha256:4d1b17af6f66b852ee3a721f6691a2ca7352f9d28f570a6a48cee4ebe646b2fd
image: postgres:16@sha256:09f23e02d76670d3b346a3c00aa33a27cf57aab8341eedfcdaed41459d14f5c4
env:
POSTGRES_PASSWORD: postgres
options: >-
@@ -34,7 +34,7 @@ jobs:
ports:
- 5432/tcp
postgres12:
image: postgres:12@sha256:aafc7d3faafa5f95fa4709007c742864747a104c3d950f98bb606145208e4a77
image: postgres:12@sha256:8ffdca6e9208aceb30653fa0cc1c38956303ace453944b54b4f71019cfe854c1
env:
POSTGRES_PASSWORD: postgres
options: >-
@@ -45,7 +45,7 @@ jobs:
ports:
- 5432/tcp
mysql8:
image: mysql:8@sha256:d7c20c5ba268c558f4fac62977f8c7125bde0630ff8946b08dde44135ef40df3
image: mysql:8@sha256:2a9ef1075ff30c65bbcf4f96b25a03ea3b3f492c284e6c4a612c269ce4c5bb19
env:
MYSQL_ROOT_PASSWORD: root
options: >-
+3 -3
View File
@@ -117,7 +117,7 @@ jobs:
# Create/Update comment with action deployment status
- name: Create or Update Comment with Deployment Notification
id: notification
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }}
@@ -165,7 +165,7 @@ jobs:
echo "Access the \`backstage\` endpoint at [\`${BACKSTAGE_HOST}\`](http://${BACKSTAGE_HOST})" >> $GITHUB_STEP_SUMMARY
- name: Create or Update Comment with Deployment URL
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.notification.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
@@ -229,7 +229,7 @@ jobs:
direction: last
- name: Update Comment with Deletion
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }}
@@ -7,7 +7,7 @@ on:
branches: [master]
paths-ignore:
- 'lighthouserc.js'
- '.github/workflows/verify_accessibility_core.yml'
- '.github/workflows/verify_accessibility.yml'
- 'plugins/catalog/src/**'
- 'plugins/catalog-react/src/**'
- 'plugins/techdocs/src/**'
+2 -2
View File
@@ -1,11 +1,11 @@
name: Accessibility
on:
# NOTE: If you change these you must update verify_accessibility_core-noop.yml as well
# NOTE: If you change these you must update verify_accessibility-noop.yml as well
pull_request:
branches: [master]
paths:
- 'lighthouserc.js'
- '.github/workflows/verify_accessibility_core.yml'
- '.github/workflows/verify_accessibility.yml'
- 'plugins/catalog/src/**'
- 'plugins/catalog-react/src/**'
- 'plugins/techdocs/src/**'
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
services:
postgres:
image: postgres:12@sha256:aafc7d3faafa5f95fa4709007c742864747a104c3d950f98bb606145208e4a77
image: postgres:12@sha256:8ffdca6e9208aceb30653fa0cc1c38956303ace453944b54b4f71019cfe854c1
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@@ -0,0 +1,31 @@
# NO-OP placeholder that always passes for other paths
# This is here so that we're able to set the status check as required
name: Microsite Accessibility
on:
pull_request:
branches: [master]
paths-ignore:
- '.github/workflows/verify_microsite_accessibility.yml'
- 'microsite/scripts/**'
- 'microsite/src/**'
- 'microsite/data/**'
- 'microsite/blog/**'
- 'microsite/static/**'
- 'beps/**'
- 'mkdocs.yml'
- 'docs/**'
permissions:
contents: read
jobs:
noop:
name: Microsite Accessibility
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
egress-policy: audit
- run: echo NOOP
@@ -0,0 +1,40 @@
name: Microsite Accessibility
on:
# NOTE: If you change these you must update verify_microsite_accessibility-noop.yml as well
pull_request:
branches: [master]
paths:
- '.github/workflows/verify_microsite_accessibility.yml'
- 'microsite/**'
- 'beps/**'
- 'mkdocs.yml'
- 'docs/**'
jobs:
lhci:
name: Microsite Accessibility
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
egress-policy: audit
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Use Node.js 18.x
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: 18.x
- name: top-level install
run: yarn install --immutable
- name: yarn install
run: yarn install --immutable
working-directory: microsite
- name: run Lighthouse CI
run: |
yarn dlx @lhci/cli@0.11.x --config=microsite/lighthouserc.js autorun
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -51,7 +51,7 @@ jobs:
- run: yarn build-storybook
- uses: chromaui/action@05a82adb1e6919df177f54777e81a2ef3e312323 # v10
- uses: chromaui/action@c53f50952a7b6d99666d77d8a61eb1b58944d68a # v10
with:
token: ${{ secrets.GITHUB_TOKEN }}
# projectToken intentionally shared to allow collaborators to run Chromatic on forks
+4 -1
View File
@@ -162,4 +162,7 @@ e2e-test-report/
*svg.dtmp
# Scripts
plugins-report.csv
plugins-report.csv
# Temporary knip configs
knip.json
+1
View File
@@ -6,6 +6,7 @@ coverage
templates
api-report.md
api-report-*.md
knip-report.md
cli-report.md
plugins/scaffolder-backend/sample-templates
.vscode
+7 -8
View File
@@ -37,14 +37,13 @@ Team: @backstage/discoverability-maintainers
Scope: Discoverability within Backstage, including the home page, information architecture, and search
| Name | Organization | Team | GitHub | Discord |
| ------------------------ | ------------ | ---- | ---------------------------------------- | ------------------ |
| Avantika Iyer | Spotify | BUX | [tikabom](http://github.com/tikabom) | - |
| Camila Belo | Spotify | BUX | [camilaibs](http://github.com/camilaibs) | Camila Loiola#0226 |
| Emma Indal | Spotify | BUX | [emmaindal](http://github.com/emmaindal) | emmaindal#7503 |
| Raghunandan Balachandran | Spotify | BUX | [soapraj](http://github.com/soapraj) | raghunandanb#1114 |
| Renan Mendes Carvalho | Spotify | BUX | [aitherios](http://github.com/aitherios) | aitherios#0593 |
| Bailey Brooks | Spotify | BUX | [bailey](http://github.com/bailey) | \_bailey4 |
| Name | Organization | Team | GitHub | Discord |
| ------------------------ | ------------ | -------------- | ---------------------------------------- | ----------------- |
| Avantika Iyer | Spotify | Infinite Buck$ | [tikabom](http://github.com/tikabom) | - |
| Emma Indal | Spotify | Infinite Buck$ | [emmaindal](http://github.com/emmaindal) | emmaindal#7503 |
| Djamaile Rahamat | Spotify | Infinite Buck$ | [djamaile](http://github.com/djamaile) | dyoqi |
| Raghunandan Balachandran | Spotify | Infinite Buck$ | [soapraj](http://github.com/soapraj) | raghunandanb#1114 |
| Bailey Brooks | Spotify | Infinite Buck$ | [bailey](http://github.com/bailey) | \_bailey4 |
### Helm Charts
+176 -34
View File
@@ -54,6 +54,7 @@ The following goals are the primary focus of this BEP:
- Basic improvements to the service-to-service auth service interfaces such that we are confident that we do not need to break them in the near future.
- If possible we will keep using the existing symmetrical keys that are used today, but it is likely that we will need to break compatibility of existing tokens.
- Encapsulation of user credentials in upstream service requests, avoiding the pattern where backend plugins re-use the user token directly for their outgoing requests.
- Separate out the ownership information out of the Backstage user tokens, since user tokens have been growing large enough to have an impact on performance and reliability.
### Non-Goals
@@ -72,6 +73,8 @@ In order to allow either unauthenticated access or cookie-based access, a plugin
For service-to-service communication we will move away from reusing user tokens in upstream requests. We will instead implement an "On-Behalf-Of" flow where incoming user credentials are encapsulated in a service token for the upstream request. In line with this the new auth service interfaces will aim to make it difficult to directly forward credentials from incoming requests, and instead encourage that plugin backends issue new service credentials for upstream requests.
An issue that has been identified in the current auth implementation is that the user information embedded in the Backstage user tokens can grow fairly large. In order to avoid that this becomes a widespread problem, especially as we implement cookie auth with a 4kb size limit, we will remove the ownership entity refs (`ent` claim) from the user tokens. There were already very few consumers of this information in practice - only the `permission-backend` and `signal-backend` plugin packages currently rely on this information. The ownership data will instead be available via a new `UserInfoService`, owned by the `auth-backend`. The implementation of this new service will keep relying on the `ent` claim of the user token initially, but we will also implement a new `/v1/userinfo` endpoint in the `auth-backend` that will migrate to transparently in the future.
## Design Details
### `AuthService` Interface
@@ -79,29 +82,54 @@ For service-to-service communication we will move away from reusing user tokens
The new `AuthService` interface is defined as follows:
```ts
export type BackstageCredentials = {
token: string;
// These credential types are opaque and will also store some internal information, for example bearer tokens
user?: {
userEntityRef: string;
ownershipEntityRefs: string[];
};
export type BackstageUserCredentials = {
$$type: '@backstage/BackstageCredentials';
service?: {
id: string;
};
type: 'user';
userEntityRef: string;
};
export type BackstageServiceCredentials = {
$$type: '@backstage/BackstageCredentials';
type: 'service';
// Exact format TBD, possibly 'plugin:<pluginId>' or 'external:<externalServiceId>'
subject: string;
// Not implemented in the first iteration, but this is how we might extend this in the future
permissions?: string[];
};
type BackstageCredentials =
| BackstageUserCredentials
| BackstageServiceCredentials;
export interface AuthService {
authenticate(token: string): Promise<BackstageCredentials>;
// TODO: should the caller provide the target plugin ID?
// TODO: how can we make it very difficult to forget to forward credentials
issueToken(credentials: BackstageCredentials): Promise<{ token: string }>;
}
```
### `AuthService` Usage Patterns
### `UserInfoService` Interface
TODO
The new `UserInfoService` interface is defined as follows:
```ts
export interface UserInfoService {
getUserInfo(
credentials: BackstageUserCredentials,
): Promise<{ ownershipEntityRefs: string[] /* profile info too? */ }>;
}
```
The `UserInfoService` is exported by `@backstage/auth-node`, and the initial implementation will simply read the ownership refs from the `ent` claim of the underlying token of the user credentials. The next iteration will instead call the `/v1/userinfo` endpoint of the `auth-backend`, once that has been implemented.
### `HttpRouterService` Interface
@@ -196,14 +224,27 @@ export default createBackendPlugin({
The new `HttpAuthService` interface is defined as follows:
```ts
export type BackstageUnauthorizedCredentials = {
$$type: '@backstage/BackstageCredentials';
type: 'unauthorized';
};
type BackstageCredentialTypes = {
user: BackstageUserCredentials;
service: BackstageServiceCredentials;
unauthorized: BackstageUnauthorizedCredentials;
};
export interface HttpAuthService {
createHttpPluginRouterMiddleware(options: OptionsTBD): Handler;
credentials(
credentials<TAllowed extends keyof BackstageCredentialTypes>(
req: Request,
options?: HttpAuthServiceMiddlewareOptions,
): BackstageCredentials;
options?: HttpAuthServiceMiddlewareOptions<TAllowed>,
): Promise<BackstageCredentialTypes[TAllowed]>;
// TODO: Keep an eye on this, might not be needed
requestHeaders(
credentials: BackstageCredentials,
): Promise<Record<string, string>>;
@@ -212,7 +253,7 @@ export interface HttpAuthService {
}
```
### `HttpAuthService` Usage Patterns
### `AuthService`, `HttpAuthService` and `UserInfoService` Usage Patterns
All of these usages patterns are from the perspective of a plugin backend.
@@ -221,15 +262,10 @@ All of these usages patterns are from the perspective of a plugin backend.
```ts
// All routes only allow authenticated users and services by default.
router.get('/read-data', (req, res) => {
// TODO: user can currently be undefined, figure out best pattern to avoid that
const { user } = httpAuth.credentials(req);
if (!user) {
throw new NotAllowedError(
'Service requests are not allowed on this endpoint',
);
}
const credentials = await httpAuth.credentials(req, { allow: ['user'] }); // throws if not: user (or obo), user-cookie
const { ownershipEntityRefs } = await userInfo.getUserInfo(credentials);
console.log(
`User ref=${user.userEntityRef} ownership=${user.ownershipEntityRefs}`,
`User ref=${credentials.userEntityRef} ownership=${ownershipEntityRefs} claims=${credentials.extraClaims}`,
);
// ...
});
@@ -238,11 +274,36 @@ router.get('/read-data', (req, res) => {
#### Forward the user credentials from an incoming requests to upstream plugin backend
```ts
class CatalogIntegration {
async getEntity(
res: string,
options: {
credentials: BackstageUserCredentials | BackstageServiceCredentials;
},
) {
return catalogClient.getEntityByRef(req.params.entityRef, {
token: await auth.issueToken({
forward: options.credentials,
}),
});
}
}
// Earlier in the router setup
const catalogIntegration = new CatalogIntegration();
router.get('/read-data', (req, res) => {
// The catalogClient will have a reference to the (plugin scoped) HttpAuthService,
// which it uses to create the credential headers for the upstream request.
const entity = await catalogClient.getEntityByRef(req.params.entityRef, {
credentials: httpAuth.credentials(req),
credentials: httpAuth.forwardCredentials(req, {
dangerouslyAllowUnauthenticated: true,
}),
});
// TODO: try this out in more places in plugins
const entity = await catalogIntegration.getEntity(req.params.entityRef, {
credentials: await httpAuth.credentials(req),
});
// ...
});
@@ -252,15 +313,17 @@ router.get('/read-data', (req, res) => {
```ts
router.get('/read-data', (req, res) => {
const credentials = httpAuth.credentials(req);
if (credentials.user) {
res.json(
// Silly example just to highlight separate code paths for user and
// service requests
todoStore.listOwnedTodos({ owner: credentials.user.userEntityRef }),
);
const credentials = await httpAuth.credentials(req, {
allow: ['user', 'service'],
});
if (credentials.type === 'user') {
res.json(todoStore.listOwnedTodos({ owner: credentials.userEntityRef }));
} else {
res.json(todoStore.listTodos());
res.json(
todoStore.listTodos({
serviceId: credentials.subject,
}),
);
}
});
```
@@ -294,9 +357,10 @@ router.get(
(req, res) => {
// These credentials don't actually contain an underlying user token for cookie-authed requests
// If you try to pass them to the AuthService, it'll throw.
const { user } = httpAuth.credentials(req);
const credentials = await httpAuth.credentials(req, { allow: ['user'] });
const { ownershipEntityRefs } = await userInfo.getUserInfo(credentials);
console.log(
`User ref=${user.userEntityRef} ownership=${user.ownershipEntityRefs}`,
`User ref=${credentials.userEntityRef} ownership=${ownershipEntityRefs}`,
);
// ...
},
@@ -309,6 +373,19 @@ The existing `IdentityService` and `TokenManagerService` will be deprecated and
The release plan for the `HttpAuthService` is TBD, but is likely to be shipped as a no-op for plugins using the old backend system. The goal is for all plugins using the new backend system to have endpoint security be opt-out, which will be a breaking change.
### Implementation Tasks
- [ ] Implement `AuthService`
- [ ] Implement `HttpAuthService` - leave cookie auth as unimplemented for now
- [ ] Add `configure()` for `HttpRouterService`, using `HttpAuthService`
- [ ] Implement a compatibility wrapper in `backend-common` that accepts `AuthService`, `HttpAuthService`, `IdentityService`, and `TokenManagerService` (all optional), and returns implementations for `AuthService` and `HttpAuthService`, such hat existing plugins can use a single `createRouter` implementation for both the old and new backend systems.
- [ ] Implement `UserInfoService` in `@backstage/auth-node` - for now it will just extract the ownership entity refs from the token stored in the credentials
- [ ] Implement cookie auth in `HttpAuthService` - just put the user token in the cookie for now
- [ ] Migrate plugins:
- [ ] Permission backend
- [ ] TechDocs backend
- [ ] App backend
## Dependencies
No significant dependencies have been identified for this work, although any future security audits of Backstage are considered dependent on this work.
@@ -316,3 +393,68 @@ No significant dependencies have been identified for this work, although any fut
## Alternatives
An alternative to built-in protection from external access would be to keep relying on external mechanisms to protect access to Backstage. We feel that this is a suboptimal solution since it adds complexity to the adoption of Backstage, and increases the risk of misconfiguration and security breaches. Regardless of whether we add built-in protection or not the ability to protect API endpoints needs to be addressed in some way, since it is a requirement for the permission system to work. This means that the extra steps to ensure protection out of the box are fairly minimal when looking at just the delta for protecting API access.
### Access Control Patterns
These are the different patterns that we've considered for how plugins should control access to their endpoints.
#### Separate methods / configuration for `use`
This approach extends the `HttpRouterService` with separate methods or options for specifying the access control for different handlers.
Pros:
- We can make strict access control the default, making relaxed controls an opt-in
- The routing setup is quite explicit in what handlers allow for what access levels
Cons:
- Forces separation of the router, splitting it into separate handlers for different levels of access.
- Can be extremely confusing because the top-level middleware for more lax access will also apply to the more strict access levels. For example
```ts
const cookieRouter = Router();
cookieRouter.use(rateLimit());
http.useWithCookieAuthentication(cookieRouter);
const mainRouter = Router();
// rateLimit() will apply here too
http.use(mainRouter);
```
This applied to any similar way of structuring this API, such as a single `.use()` method with additional options:
```ts
http.use(cookieRouter, { allow: ['user-cookie'] });
```
#### Separate configuration on different paths for `use`
Similar to the previous approach, but also require that a path is provided. This removes much of the confusion around what middleware are applied.
The downside of this approach is that it still has the drawback of forcing a separation of the router, but at the same it provides very little benefit over a top-level path configuration approach like `http.configure()`. The `'/static'` path in the below example essentially has the exact same logic as `.configure({ cookieAuthPaths: ['/static'] })` since it'd be implemented in the same way. The `.configure()` approach has the benefit of allowing plugin authors to decide whether they want to keep the routes separate or not.
This does have the benefit of letting the framework know which exact routes are protected, which can be useful for introspection, although that benefit also applies to the `.configure()` approach.
```ts
// This isn't too bad, but it's extremely similar to the configure() method since
// we're just matching on the path. The benefit of configure is that it allows you
// to keep everything in a singe router if desired.
http.use('/static', cookieRouter, { allow: ['user-cookie'] });
```
#### Complete opt-out
This approach simply enabled plugins to opt-out of the default access control, and instead require that they implement the necessary endpoint protection using `httpAuth.middleware()`.
This approach makes it a bit easier to make mistakes compared to the `.configure()` approach, but at the same time it has the benefit of collection all access control login in a single place (the plugin router). It also doesn't allow the framework to see which endpoints have relaxed protection, which is a downside.
Still, this is a pattern that is currently second in line if we don't go with the `.configure()` approach.
```ts
http.dangerouslyDisableAuthentication();
```
#### Leave access control to the plugin router
Having strict access control be the default with explicit opt-out is an explicit goal of this work, so this is not an option that we are considering.
+1 -1
View File
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/devcontainers/typescript-node:20@sha256:185cde4e033cd68fb4c5c9eef94ba6bde0dac2f334ecd1a7cf6cd2c87f712d85
FROM mcr.microsoft.com/devcontainers/typescript-node:20@sha256:381a1fde32704f229b439ade94816d5619f710e35698982ec35afcd9384ae6cf
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install chromium \
@@ -46,7 +46,7 @@ RUN yarn workspace app build
FROM nginx:mainline@sha256:5f44022eab9198d75939d9eaa5341bc077eca16fa51d4ef32d33f1bd4c8cbe7d
FROM nginx:mainline@sha256:84c52dfd55c467e12ef85cad6a252c0990564f03c4850799bf41dd738738691f
RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/*
@@ -27,7 +27,7 @@
FROM nginx:mainline@sha256:5f44022eab9198d75939d9eaa5341bc077eca16fa51d4ef32d33f1bd4c8cbe7d
FROM nginx:mainline@sha256:84c52dfd55c467e12ef85cad6a252c0990564f03c4850799bf41dd738738691f
RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/*
+3 -1
View File
@@ -220,6 +220,8 @@ clientSecret: someGithubAppClientSecret
webhookSecret: someWebhookSecret
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
SomeRsaPrivateKey
SomeRsaPrivateKeySecurelyStored
-----END RSA PRIVATE KEY-----
```
**Warning: Sensitive information, such as private keys, should not be hard coded**. We recommend that this entire file should be a secret and stored as such in a secure storage solution like Vault, to ensure they are neither exposed nor misused. This example key part only shows the format on how to use the yaml | syntax to make sure that the key is valid.
@@ -81,8 +81,6 @@ const extension = createExtension({
Note that while the `createExtension` is public API and used in many places, it is not typically what you use when building plugins and features. Instead there are many extension creator functions exported by both the core APIs and plugins that make it easier to create extensions for more specific usages.
... TODO ...
## Extension Data
Communication between extensions happens in one direction, from one child extension through the attachment point to its parent. The child extension outputs data which is then passed as inputs to the parent extension. This data is called Extension Data, where the shape of each individual piece of data is described by an Extension Data Reference. These references are created separately from the extensions themselves, and can be shared across multiple different kinds of extensions. Each reference consists of an ID and a TypeScript type that the data needs to conform to, and represents one type of data that can be shared between extensions.
@@ -137,7 +135,7 @@ const extension = createExtension({
We provide default `coreExtensionData`, which provides commonly used `ExtensionDataRef`s - e.g. for `React.JSX.Element` and `RouteRef`. They can be used when creating your own extension. For example, the React Element extension data that we defined above is already provided as `coreExtensionData.reactElement`.
<!-- For a full list and explanations of all types of core extension data, see the [core extension data reference](#TODO). -->
For a full list and explanations of all types of core extension data, see the [core extension data reference](../building-plugins/04-built-in-data-refs.md).
### Optional Extension Data
@@ -0,0 +1,148 @@
---
id: index
title: Building Frontend Apps
sidebar_label: Overview
# prettier-ignore
description: Building frontend apps using the new frontend system
---
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
To get set up quickly with your own Backstage project you can create a Backstage App.
A Backstage App is a monorepo setup that includes everything you need to run Backstage in your own environment.
## Creating a new app
To create a new Backstage app we recommend using the `@backstage/create-app` command line, and the easiest way to run this package is with `npx`:
:::note
The create-app CLI requires Node.js Active LTS Release.
:::
```sh
# The command bellow creates a Backstage App inside the current folder.
# The name of the app-folder is the name that was provided when prompted.
npx @backstage/create-app@latest
```
The created-app is currently templated for legacy frontend system applications, so the app wiring code it creates needs to be migrated, see [the app instance](#the-app-instance) section for an example.
## The app instance
The starting point of a frontend app is the `createApp` function, which accepts a single options object as its only parameter. It is imported from `@backstage/frontend-app-api`, which is where you will find most of the common APIs for building apps.
This is how to create a minimal app:
```tsx title="in src/index.ts"
import ReactDOM from 'react-dom/client';
import { createApp } from '@backstage/frontend-app-api';
import catalogPlugin from '@backstage/plugin-catalog/alpha';
// Create your app instance
const app = createApp({
// Features such as plugins can be installed explicitly, but we will explore other options later on
features: [catalogPlugin],
});
// This creates a React element that renders the entire app
const root = app.createRoot();
// Just like any other React we need a root element. No server side rendering is used.
const rootEl = document.getElementById('root')!;
ReactDOM.createRoot(rootEl).render(root);
```
Note that `createRoot` returns the root element that is rendered by React. The above example is installing a catalog plugin and using default settings for the app, as no options other than the `features` array are passed to the `createApp` function.
Visit the [built-in extensions](#customize-or-override-built-in-extensions) section to see what is installed by default in a Backstage application.
## Configure your app
### Bind external routes
Linking routes from different plugins requires this configuration. You can do this either through a configuration file or by coding, visit [this](https://backstage.io/docs/frontend-system/architecture/routes#binding-external-route-references) page for instructions.
### Enable feature discovery
Use this setting to enable experimental feature discovery when building your app with `@backstage/cli`. With this configuration your application tries to discover and install package extensions automatically, check [here](../architecture/02-app.md#feature-discovery) for more details.
:::warning
Remember that package extensions that are not auto-discovered must be manually added to the application when creating an app. See [features](#install-features-manually) for more details.
:::
### Configure extensions individually
It is possible to enable, disable and configure extensions individually in the `app-config.yaml` config file. To get familiar with what is available for app extensions personalization, go to the [built-in extensions](./02-built-in-extensions.md) documentation. For plugin customizations, we recommend that you read the instructions in each plugin's README file.
### Customize or override built-in extensions
Previously you would customize the application route, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want you to write less code and install more extensions to customize your Backstage instance. See [here](../building-plugins/03-extension-types.md) which types of extensions are available for you to customize your application.
## Use code to customize the app at a more granular level
### Install features manually
A manual installation is required if your packages are not discovered automatically, either because you are not using `@backstage/cli` to build your application or because the features are defined in local modules in the app package. In order to manually install a feature, you must import it and pass it to the `createApp` function:
```tsx title="packages/app/src/App.tsx"
import { createApp } from '@backstage/frontend-app-api';
// This plugin was create as a local module in the app
import { somePlugin } from './plugins';
const app = createApp({
features: [somePlugin],
});
export default app.createRoot();
```
:::info
You can also pass overrides to the features array, for more details, please read the [extension overrides](../architecture/05-extension-overrides.md) documentation.
:::
### Using an async features loader
In case you need to perform asynchronous operations before passing features to the `createApp` function, define a [feature loader](https://backstage.io/docs/reference/frontend-app-api.createappfeatureloader/) object and pass it to the `features` option:
```tsx title="packages/app/src/App.tsx"
import { createApp } from '@backstage/frontend-app-api';
const app = createApp({
features: {
getLoaderName: () => '<your-custom-features-loader-name>',
// there is a reference to the config api in the options param
load: async _options => {
// returning a lazy loaded plugins and overrides array
// could be util for module federation
return import('./features').then(m => m.default);
},
},
});
export default app.createRoot();
```
### Lazy load your configuration file
In some cases we want to load our configuration from a backend server and to do so, you can pass an callback to the `configLoader` option when calling the `createApp` function, the callback should return a promise of an object with the config object:
```tsx title="packages/app/src/App.tsx"
import { createApp } from '@backstage/frontend-app-api';
import { getConfigFromServer } from './utils';
// Example lazy loading the app configuration
const app = createApp({
// Returns Promise<{ config: ConfigApi }>
configLoader: async () => {
// Calls an async utility method that fetches the config object from the server
const config = await getConfigFromServer();
// Feel free to manipulate the config object before returning it
// A common example is conditionally modify the config based on the running enviroment
return { config };
},
});
export default app.createRoot();
```
@@ -0,0 +1,190 @@
---
id: built-in-extensions
title: App Built-in Extensions
sidebar_label: Built-in extensions
# prettier-ignore
description: Configuring or overriding built-in extensions
---
Built-in extensions are default app extensions that are always installed when you create a Backstage app.
## Disable built-in extensions
All built-in extensions can be disabled in the same way as you disable any other extension:
```yaml title="app-config.yaml"
extensions:
# Disabling the built-in app root alert element
- app-root-element:app/alert-display: false
```
:::warning
Be careful when disabling built-in extensions, as there may be other extensions depending on their existence. For example, the built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/docs/reference/core-plugin-api.alertapi) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
:::
## Override built-in extensions
You can override any built-in extension whenever their customizations, whether through configuration or input, do not meet a use case for your Backstage instance. Check out [this](../architecture/05-extension-overrides.md) documentation on how to override application extensions.
:::warning
Be aware there could be some implementation requirements to properly override an built-in extension, such as using same apis and do not remove inputs or configurations otherwise you can cause a side effect in other parts of the system that expects same minimal behavior.
:::
## Default built-in extensions
### App
This extension is the first extension attached to the extension tree. It is responsible for receiving the application's root element and other Frontend framework inputs.
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| root | The app root element. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | The [`App/Root`](#app-root) extension output. | No creator available, configure or override the [`App/Root`](#app-root) extension. |
| apis | The app apis factories. | [createApiExtension.factoryDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createapiextension.factorydataref) | false | See [default apis](#default-apis-extensions). | [createApiExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapiextension) |
| themes | The app themes list. | [createThemeExtension.themeDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension.themedataref) | false | See [default themes](#default-theme-extensions). | [createThemeExtension](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension) |
| components | The app components list. | [createComponentExtension.componentDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension.componentdataref) | false | See [default components](#default-components-extensions). | [createComponentExtension](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension) |
| translations | The app translations list. | [createTranslationExtension.translationDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension.translationdataref) | false | - | [createTranslationExtension](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension) |
#### Default theme extensions
Extensions that provides default theme inputs for the `App` extension.
| kind | namespace | name | id |
| :---: | :-------: | :---: | :---------------: |
| theme | app | light | `theme:app/light` |
| theme | app | dark | `theme:app/dark` |
#### Default components extensions
Extensions that provides default components inputs for the `App` extension.
| kind | namespace | name | id |
| :--------: | :-------: | :-----------------------------------: | :----------------------------------------------------: |
| components | app | core.components.progress | `components:app/core.components.progress` |
| components | app | core.components.notFoundErrorPage | `components:app/core.components.notFoundErrorPage` |
| components | app | core.components.errorBoundaryFallback | `components:app/core.components.errorBoundaryFallback` |
#### Default apis extensions
Extensions that provides default apis inputs for the `App` extension.
| kind | namespace | name | id |
| :--: | :------------------------: | :--: | :------------------------------: |
| api | core.discovery | - | `api:core.discovery` |
| api | core.alert | - | `api:core.alert` |
| api | core.analytics | - | `api:core.analytics` |
| api | core.error | - | `api:core.error` |
| api | core.storage | - | `api:core.storage` |
| api | core.fetch | - | `api:core.fetch` |
| api | core.oauthrequest | - | `api:core.oauthrequest` |
| api | core.auth.google | - | `api:core.auth.google` |
| api | core.auth.microsoft | - | `api:core.auth.microsoft` |
| api | core.auth.github | - | `api:core.auth.github` |
| api | core.auth.okta | - | `api:core.auth.okta` |
| api | core.auth.gitlab | - | `api:core.auth.gitlab` |
| api | core.auth.onelogin | - | `api:core.auth.onelogin` |
| api | core.auth.bitbucket | - | `api:core.auth.bitbucket` |
| api | core.auth.bitbucket-server | - | `api:core.auth.bitbucket-server` |
| api | core.auth.atlassian | - | `api:core.auth.atlassian` |
| api | plugin.permission.api | - | `api:plugin.permission.api` |
### App root
This is the extension that creates the app root element, so it renders root level components such as app router and layout.
#### Inputs
| Name | Description | Requirements | Optional | Default | Extension creator |
| ---------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| router | A React component that should manager the app routes context. | It must be one [router](https://reactrouter.com/en/main/routers/picking-a-router#web-projects) component or a custom component compatible with the 'react-router' library. | true | [BrowserRouter](https://reactrouter.com/en/main/router-components/browser-router) | [createRouterExtension](https://backstage.io/docs/reference/frontend-plugin-api.createrouterextension) |
| signInPage | A React component that should render the app sign-in page. | Should call the `onSignInSuccess` prop when the user has been successfully authorized, otherwise the user will not be correctly redirected to the application home page. | true | The default `AppRoot` extension does not use a default component for this input, it bypasses the user authentication check and always renders all routes when a login page is not installed. | [createSignInPageExtension](https://backstage.io/docs/reference/frontend-plugin-api.createsigninpageextension/) |
| children | A React component that renders the app sidebar and main content in a particular layout. | - | false | The [`App/Layout`](#app-layout) extension output. | No creator available, configure or override the [`App/Layout`](#app-layout) extension. |
| elements | React elements to be rendered outside of the app layout, such as shared popups. | - | false | See [default elements](#default-app-root-elements-extensions). | [createAppRootElementExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapprootelementextension/) |
| wrappers | React components that should wrap the root element. | - | true | - | [createAppRootWrapperExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapprootwrapperextension/) |
#### Default app root elements extensions
##### Alert Display
An app root element extension that displays messages posted via the [`AlertApi`](https://backstage.io/docs/reference/core-plugin-api.alertapi).
| kind | namespace | name | id |
| :--------------: | :-------: | :-----------: | :----------------------------------: |
| app-root-element | app | alert-display | `app-root-element:app/alert-display` |
###### Configurations
| Key | Type | Default value | Description |
| -------------------- | -------------------------------------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------- |
| `transientTimeoutMs` | number | 5000 | Time in milliseconds to wait before displaying messages. |
| `anchorOrigin` | { vertical: 'top' \| 'bottom', horizontal: 'left' \| 'center' \| 'right' } | { vertical: 'top', horizontal: 'center' } | Position on the screen where the message alert will be displayed. |
###### Override or disable the extension
If you do not want to display alerts, disable this extension or if the available settings do not meet your needs, override this extension.
:::warning
The built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/docs/reference/core-plugin-api.alertapi) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
:::
##### OAuth Request Dialog
An app root element extension that renders the oauth request dialog, it is based on the [oauthRequestApi](https://backstage.io/docs/reference/core-plugin-api.oauthrequestapi/).
| kind | namespace | name | id |
| :--------------: | :-------: | :------------------: | :-----------------------------------------: |
| app-root-element | app | oauth-request-dialog | `app-root-element:app/oauth-request-dialog` |
### App layout
Renders the app's sidebar and content in a specific layout.
| kind | namespace | name | id |
| :--: | :-------: | :----: | :----------: |
| - | app | layout | `app/layout` |
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------ |
| nav | A React element that renders the app sidebar. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | - | Override the `App/Nav` extension. |
| content | A React element that renders the app content. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | - | Override the `App/Routes` extension. |
### App nav
Extension responsible for rendering the logo and items in the app's sidebar.
| kind | namespace | name | id |
| :--: | :-------: | :--: | :-------: |
| - | app | nav | `app/nav` |
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ----- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------- |
| logos | A nav logos object. | [createNavLogoExtension.logoElementsDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavlogoextension.logoelementsdataref) | true | - | [createNavLogoExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavlogoextension) |
| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) |
### App routes
Renders a route element for each route received as input and a `NotFoundErrorPage` component.
| kind | namespace | name | id |
| :--: | :-------: | :----: | :----------: |
| - | app | routes | `app/routes` |
#### Caveats
Be careful when overriding this extension, as to do so correctly you must consider these implementation requirements:
- The routing system is managed by more than one extension, and they all use `react-router` behind the scenes. There are also some utilities that are based on the same `routing` library like `useRouteRefParams`. Therefore, you cannot use a different library without causing side effects in these other extensions and helper utilities;
- Don't remove configs or inputs, just extend these things yourself with optional new options, otherwise it will cause breaking changes for extensions like `createPageExtension` that depend on this type of inputs;
- Remember to user the route refs for getting paths dynamically, otherwise if an adopter modifies a path through configuration, the route is not going to point to the configured path;
- Adopters expect to be able to customize the `NotFoundErrorPage` component via Components API, you should render this component for routes not configured.
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------- |
| routes | The route objects list. | `{ path: coreExtensionData.routePath, ref: coreExtensionData.routeRef.optional(), element: coreExtensionData.reactElement }` | false | - | [createPageExtension](https://backstage.io/docs/reference/frontend-plugin-api.createpageextension) |
@@ -0,0 +1,61 @@
---
id: built-in-data-refs
title: Built-in data refs
sidebar_label: Built-in data refs
# prettier-ignore
description: Configuring or overriding built-in extension data references
---
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
To have a better understanding of extension data references please read [the corresponding architecture section](../architecture/03-extensions.md#extension-data) first.
## Built-in extension data references
Data references help to define the inputs and outputs of an extension. A data ref is uniquely identified through its `id`. Through the data ref, strong typing is enforced for the input/output of the extension.
### `reactElement`
| id | type |
| :-----------------: | :-----------: |
| `core.reactElement` | `JSX.Element` |
The `reactElement` data reference can be used for defining the extension input/output of React elements. Example usage could be something like this:
```tsx
import {
coreExtensionData,
createExtensionInput,
createPageExtension,
} from '@backstage/frontend-plugin-api';
const homePage = createPageExtension({
defaultPath: '/home',
routeRef: rootRouteRef,
inputs: {
props: createExtensionInput({
children: coreExtensionData.reactElement.optional(),
}),
},
});
```
### `routePath`
| id | type |
| :-----------------: | :------: |
| `core.routing.path` | `string` |
The `routePath` data reference can be used for defining the extension input/output of string paths.
### `routeRef`
| id | type |
| :----------------: | :--------: |
| `core.routing.ref` | `RouteRef` |
The `routeRef` data reference can be used for defining the extension input/output of route references.
### Other data references
There are other data refs in the frontend system you might stumble upon while building your plugin. Most of them were created for use cases inside the core of the system, but they can be used also in your plugin.
+10
View File
@@ -0,0 +1,10 @@
---
title: Statuspage.io Plugin
author: AxisCommunications
authorUrl: https://github.com/AxisCommunications
category: Monitoring
description: The Statuspage plugin allows you to embedd https://statuspage.io components, component groups and dashboards in Backstage.
documentation: https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/statuspage/README.md
iconUrl: https://raw.githubusercontent.com/AxisCommunications/backstage-plugins/main/plugins/statuspage/media/logo.png
npmPackageName: '@axis-backstage/plugin-statuspage'
addedDate: '2024-02-06'
+68
View File
@@ -0,0 +1,68 @@
/*
* 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.
*/
var sidebars = require('./sidebars.json');
module.exports = {
ci: {
collect: {
url: [
/** Home */
'http://localhost:3000',
/** Docs - Getting Started */
'http://localhost:3000/docs/getting-started',
/** Docs - Software Catalog */
'http://localhost:3000/docs/features/software-catalog',
/** Docs - Create a Plugin */
'http://localhost:3000/docs/plugins/create-a-plugin',
/** Docs - Designing for Backstage */
'http://localhost:3000/docs/dls/design',
/** Blog */
'http://localhost:3000/blog',
/** Plugins */
'http://localhost:3000/plugins',
/** Demos */
'http://localhost:3000/demos',
/** Community */
'http://localhost:3000/community',
/** Releases */
...sidebars.releases['Release Notes'].map(
path => `http://localhost:3000/docs/${path}`,
),
],
settings: {
onlyCategories: ['accessibility'],
output: ['html', 'json'],
outputPath: './.lighthouseci/reports',
preset: 'desktop',
},
// refers to root package scripts
startServerCommand: 'yarn run start:microsite',
startServerReadyPattern: 'compiled successfully',
startServerReadyTimeout: 600000,
numberOfRuns: 1,
},
assert: {
assertions: {
'categories:performance': 'off',
'categories:pwa': 'off',
'categories:best-practices': 'off',
'categories:seo': 'off',
'categories:accessibility': ['error', { minScore: 0.95 }],
},
},
},
};
+10 -1
View File
@@ -413,7 +413,16 @@
"items": [
"frontend-system/building-plugins/index",
"frontend-system/building-plugins/testing",
"frontend-system/building-plugins/extension-types"
"frontend-system/building-plugins/extension-types",
"frontend-system/building-plugins/built-in-data-refs"
]
},
{
"type": "category",
"label": "Building Apps",
"items": [
"frontend-system/building-apps/index",
"frontend-system/building-apps/built-in-extensions"
]
},
{
+2 -2
View File
@@ -88,8 +88,8 @@ const Community = () => {
},
{
name: 'VMWare',
url: 'https://www.vmware.com',
logo: 'img/partner-logo-vmware.png',
url: 'https://tanzu.vmware.com/',
logo: 'img/partner-logo-tanzubybroadcom.png',
},
];
//#endregion
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

+52 -50
View File
@@ -2701,90 +2701,90 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-darwin-arm64@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-darwin-arm64@npm:1.3.107"
"@swc/core-darwin-arm64@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-darwin-arm64@npm:1.4.0"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@swc/core-darwin-x64@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-darwin-x64@npm:1.3.107"
"@swc/core-darwin-x64@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-darwin-x64@npm:1.4.0"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@swc/core-linux-arm-gnueabihf@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.107"
"@swc/core-linux-arm-gnueabihf@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.0"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@swc/core-linux-arm64-gnu@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-linux-arm64-gnu@npm:1.3.107"
"@swc/core-linux-arm64-gnu@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-linux-arm64-gnu@npm:1.4.0"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-arm64-musl@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-linux-arm64-musl@npm:1.3.107"
"@swc/core-linux-arm64-musl@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-linux-arm64-musl@npm:1.4.0"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@swc/core-linux-x64-gnu@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-linux-x64-gnu@npm:1.3.107"
"@swc/core-linux-x64-gnu@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-linux-x64-gnu@npm:1.4.0"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-x64-musl@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-linux-x64-musl@npm:1.3.107"
"@swc/core-linux-x64-musl@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-linux-x64-musl@npm:1.4.0"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@swc/core-win32-arm64-msvc@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-win32-arm64-msvc@npm:1.3.107"
"@swc/core-win32-arm64-msvc@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-win32-arm64-msvc@npm:1.4.0"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@swc/core-win32-ia32-msvc@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-win32-ia32-msvc@npm:1.3.107"
"@swc/core-win32-ia32-msvc@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-win32-ia32-msvc@npm:1.4.0"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@swc/core-win32-x64-msvc@npm:1.3.107":
version: 1.3.107
resolution: "@swc/core-win32-x64-msvc@npm:1.3.107"
"@swc/core-win32-x64-msvc@npm:1.4.0":
version: 1.4.0
resolution: "@swc/core-win32-x64-msvc@npm:1.4.0"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@swc/core@npm:^1.3.46":
version: 1.3.107
resolution: "@swc/core@npm:1.3.107"
version: 1.4.0
resolution: "@swc/core@npm:1.4.0"
dependencies:
"@swc/core-darwin-arm64": 1.3.107
"@swc/core-darwin-x64": 1.3.107
"@swc/core-linux-arm-gnueabihf": 1.3.107
"@swc/core-linux-arm64-gnu": 1.3.107
"@swc/core-linux-arm64-musl": 1.3.107
"@swc/core-linux-x64-gnu": 1.3.107
"@swc/core-linux-x64-musl": 1.3.107
"@swc/core-win32-arm64-msvc": 1.3.107
"@swc/core-win32-ia32-msvc": 1.3.107
"@swc/core-win32-x64-msvc": 1.3.107
"@swc/core-darwin-arm64": 1.4.0
"@swc/core-darwin-x64": 1.4.0
"@swc/core-linux-arm-gnueabihf": 1.4.0
"@swc/core-linux-arm64-gnu": 1.4.0
"@swc/core-linux-arm64-musl": 1.4.0
"@swc/core-linux-x64-gnu": 1.4.0
"@swc/core-linux-x64-musl": 1.4.0
"@swc/core-win32-arm64-msvc": 1.4.0
"@swc/core-win32-ia32-msvc": 1.4.0
"@swc/core-win32-x64-msvc": 1.4.0
"@swc/counter": ^0.1.1
"@swc/types": ^0.1.5
peerDependencies:
@@ -2813,14 +2813,14 @@ __metadata:
peerDependenciesMeta:
"@swc/helpers":
optional: true
checksum: 0dccff50461fb8c0f4af053b70e555c91386cb07aa7657a7328d58e397d15640723587549416d8fa7dcc073ad11b39318146bd50ec4a82345ce2ce39c7ba4c00
checksum: cef6459ba707362e88373f1c2c779c760a7fdf06c0123856be005bb012de91cb913b37fb49e485ac551494a1ee46fb6369c5aca9d453e1e5e391a8514c0db185
languageName: node
linkType: hard
"@swc/counter@npm:^0.1.1":
version: 0.1.1
resolution: "@swc/counter@npm:0.1.1"
checksum: bb974babd493ba01c0d4a95ab610c3fc15fbf609c08cb0342798e485f57ecc0950abbf84e07124e63c5fe610b492d9a8dd03701d3b9ef7329d9e8bf3cc44980f
"@swc/counter@npm:^0.1.1, @swc/counter@npm:^0.1.3":
version: 0.1.3
resolution: "@swc/counter@npm:0.1.3"
checksum: df8f9cfba9904d3d60f511664c70d23bb323b3a0803ec9890f60133954173047ba9bdeabce28cd70ba89ccd3fd6c71c7b0bd58be85f611e1ffbe5d5c18616598
languageName: node
linkType: hard
@@ -11500,12 +11500,14 @@ __metadata:
linkType: hard
"swc-loader@npm:^0.2.3":
version: 0.2.4
resolution: "swc-loader@npm:0.2.4"
version: 0.2.6
resolution: "swc-loader@npm:0.2.6"
dependencies:
"@swc/counter": ^0.1.3
peerDependencies:
"@swc/core": ^1.2.147
webpack: ">=2"
checksum: f23bfe8900b35abdcb9910a2749f3c9d66edf5c660afc67fcf7983647eaec322e024d1edd3bd9fd48bd3191eea0616f67b5f8b5f923e3a648fa5b448683c3213
checksum: fe90948c02a51bb8ffcff1ce3590e01dc12860b0bb7c9e22052b14fa846ed437781ae265614a5e14344bea22001108780f00a6e350e28c0b3499bc4cd11335fb
languageName: node
linkType: hard
+7
View File
@@ -11,10 +11,12 @@
"start-backend": "yarn workspace example-backend start",
"start:next": "yarn workspace example-app-next start",
"start-backend:next": "yarn workspace example-backend-next start",
"start:microsite": "cd microsite/ && yarn start",
"build:backend": "yarn workspace example-backend build",
"build:all": "backstage-cli repo build --all",
"build:api-reports": "yarn build:api-reports:only --tsc",
"build:api-reports:only": "backstage-repo-tools api-reports --allow-warnings 'packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-wrong-input-file-type --validate-release-tags",
"build:knip-reports": "backstage-repo-tools knip-reports",
"build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'",
"build:plugins-report": "node ./scripts/build-plugins-report",
"tsc": "tsc",
@@ -43,6 +45,10 @@
"prepare": "husky",
"postinstall": "husky || true"
},
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage"
},
"workspaces": {
"packages": [
"packages/*",
@@ -86,6 +92,7 @@
"eslint-plugin-testing-library": "^6.0.0",
"fs-extra": "10.1.0",
"husky": "^9.0.0",
"knip": "^4.3.1",
"lint-staged": "^15.0.0",
"minimist": "^1.2.5",
"node-gyp": "^10.0.0",
+2
View File
@@ -0,0 +1,2 @@
# Knip report
@@ -0,0 +1,9 @@
# Knip report
## Unused devDependencies (2)
| Name | Location |
|:------------|:-------------|
| msw | package.json |
| cross-fetch | package.json |
+82
View File
@@ -0,0 +1,82 @@
# Knip report
## Unused dependencies (64)
| Name | Location |
|:-------------------------------------------------|:-------------|
| lodash | package.json |
| history | package.json |
| react-use | package.json |
| @octokit/rest | package.json |
| zen-observable | package.json |
| @material-ui/lab | package.json |
| @backstage/theme | package.json |
| @backstage/plugin-org | package.json |
| @backstage/plugin-adr | package.json |
| @backstage/plugin-todo | package.json |
| @backstage/plugin-gocd | package.json |
| app-next-example-plugin | package.json |
| @backstage/plugin-kafka | package.json |
| @backstage/app-defaults | package.json |
| @backstage/plugin-sentry | package.json |
| @backstage/plugin-search | package.json |
| @backstage/plugin-badges | package.json |
| @backstage/catalog-model | package.json |
| @backstage/plugin-rollbar | package.json |
| @backstage/plugin-jenkins | package.json |
| @backstage/plugin-explore | package.json |
| @backstage/plugin-catalog | package.json |
| @circleci/backstage-plugin | package.json |
| @backstage/plugin-puppetdb | package.json |
| @backstage/plugin-playlist | package.json |
| @backstage/plugin-newrelic | package.json |
| @backstage/plugin-linguist | package.json |
| @backstage/plugin-devtools | package.json |
| @backstage/plugin-api-docs | package.json |
| @backstage/plugin-airbrake | package.json |
| @backstage/plugin-shortcuts | package.json |
| @backstage/plugin-dynatrace | package.json |
| @backstage/plugin-stackstorm | package.json |
| @backstage/plugin-scaffolder | package.json |
| @backstage/plugin-lighthouse | package.json |
| @backstage/plugin-kubernetes | package.json |
| @backstage/plugin-cloudbuild | package.json |
| @backstage/plugin-azure-sites | package.json |
| @backstage/plugin-search-react | package.json |
| @backstage/plugin-gcp-projects | package.json |
| @backstage/plugin-azure-devops | package.json |
| @backstage/plugin-tech-insights | package.json |
| @backstage/plugin-search-common | package.json |
| @backstage/plugin-cost-insights | package.json |
| @backstage/plugin-code-coverage | package.json |
| @backstage/plugin-catalog-react | package.json |
| @backstage/plugin-catalog-graph | package.json |
| @backstage/plugin-techdocs-react | package.json |
| @backstage/plugin-octopus-deploy | package.json |
| @backstage/plugin-github-actions | package.json |
| @backstage/plugin-catalog-common | package.json |
| @backstage/plugin-apache-airflow | package.json |
| @backstage/plugin-linguist-common | package.json |
| @backstage/plugin-entity-feedback | package.json |
| @backstage/plugin-scaffolder-react | package.json |
| @backstage/plugin-permission-react | package.json |
| @roadiehq/backstage-plugin-travis-ci | package.json |
| @roadiehq/backstage-plugin-buildkite | package.json |
| @backstage/plugin-newrelic-dashboard | package.json |
| @oriflame/backstage-plugin-score-card | package.json |
| @roadiehq/backstage-plugin-github-insights | package.json |
| @backstage/plugin-catalog-unprocessed-entities | package.json |
| @roadiehq/backstage-plugin-github-pull-requests | package.json |
| @backstage/plugin-techdocs-module-addons-contrib | package.json |
## Unused devDependencies (6)
| Name | Location |
|:----------------------------|:-------------|
| cross-env | package.json |
| @types/jquery | package.json |
| @testing-library/dom | package.json |
| @types/zen-observable | package.json |
| @testing-library/react | package.json |
| @testing-library/user-event | package.json |
+5
View File
@@ -2,6 +2,11 @@
"name": "example-app-next",
"version": "0.0.6-next.2",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/app-next"
},
"backstage": {
"role": "frontend"
},
+38
View File
@@ -0,0 +1,38 @@
# Knip report
## Unused dependencies (15)
| Name | Location |
|:----------------------------------|:-------------|
| vite | package.json |
| history | package.json |
| react-router | package.json |
| @octokit/rest | package.json |
| zen-observable | package.json |
| vite-plugin-html | package.json |
| @material-ui/lab | package.json |
| @backstage/theme | package.json |
| @vitejs/plugin-react | package.json |
| vite-plugin-node-polyfills | package.json |
| @backstage/plugin-airbrake | package.json |
| @backstage/frontend-app-api | package.json |
| @backstage/plugin-azure-sites | package.json |
| @backstage/plugin-search-common | package.json |
| @backstage/plugin-linguist-common | package.json |
## Unused devDependencies (5)
| Name | Location |
|:----------------------------|:-------------|
| cross-env | package.json |
| @types/jquery | package.json |
| @testing-library/dom | package.json |
| @types/zen-observable | package.json |
| @testing-library/user-event | package.json |
## Unlisted dependencies (1)
| Name | Location |
|:------------|:---------------------------------------------------------|
| @rjsf/utils | src/components/scaffolder/customScaffolderExtensions.tsx |
+8
View File
@@ -0,0 +1,8 @@
# Knip report
## Unused dependencies (1)
| Name | Location |
|:------------------|:-------------|
| winston-transport | package.json |
+24
View File
@@ -0,0 +1,24 @@
# Knip report
## Unused dependencies (3)
| Name | Location |
|:----------------------|:-------------|
| mysql2 | package.json |
| @types/webpack-env | package.json |
| @manypkg/get-packages | package.json |
## Unused devDependencies (3)
| Name | Location |
|:-------------------|:-------------|
| mysql2 | package.json |
| better-sqlite3 | package.json |
| @types/webpack-env | package.json |
## Referenced optional peerDependencies (1)
| Name | Location |
|:---------------------|:-------------|
| pg-connection-string | package.json |
+8
View File
@@ -0,0 +1,8 @@
# Knip report
## Unused devDependencies (1)
| Name | Location |
|:------------------------------|:-------------|
| @backstage/backend-test-utils | package.json |
+2 -2
View File
@@ -33,10 +33,10 @@
},
"dependencies": {
"@backstage/backend-app-api": "workspace:^",
"@backstage/backend-common": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-common": "workspace:^"
},
"devDependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^"
},
@@ -0,0 +1,2 @@
# Knip report
@@ -0,0 +1,9 @@
# Knip report
## Unused dependencies (2)
| Name | Location |
|:--------------------------------|:-------------|
| @backstage/errors | package.json |
| @backstage/plugin-search-common | package.json |
@@ -9,6 +9,11 @@
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/backend-dynamic-feature-service"
},
"backstage": {
"role": "node-library"
},
+14
View File
@@ -0,0 +1,14 @@
# Knip report
## Unused dependencies (7)
| Name | Location |
|:-------------------------------------------------|:-------------|
| @backstage/backend-tasks | package.json |
| @backstage/plugin-auth-node | package.json |
| @backstage/backend-plugin-api | package.json |
| @backstage/plugin-permission-node | package.json |
| @backstage/plugin-permission-common | package.json |
| @backstage/plugin-search-backend-node | package.json |
| @backstage/plugin-catalog-backend-module-openapi | package.json |
@@ -0,0 +1,9 @@
# Knip report
## Unused dependencies (2)
| Name | Location |
|:------------------------------|:-------------|
| lodash | package.json |
| @backstage/backend-plugin-api | package.json |
@@ -0,0 +1,2 @@
# Knip report
+2
View File
@@ -0,0 +1,2 @@
# Knip report
@@ -0,0 +1,12 @@
# Knip report
## Unused dependencies (5)
| Name | Location |
|:------------------|:-------------|
| pg | package.json |
| msw | package.json |
| mysql2 | package.json |
| better-sqlite3 | package.json |
| @backstage/errors | package.json |
+26
View File
@@ -0,0 +1,26 @@
# Knip report
## Unused dependencies (12)
| Name | Location |
|:--------------------------------------------------|:-------------|
| pg | package.json |
| luxon | package.json |
| mysql2 | package.json |
| example-app | package.json |
| @octokit/rest | package.json |
| better-sqlite3 | package.json |
| @gitbeaker/node | package.json |
| pg-connection-string | package.json |
| azure-devops-node-api | package.json |
| @backstage/plugin-tech-insights-node | package.json |
| @backstage/plugin-azure-sites-common | package.json |
| @backstage/plugin-scaffolder-backend-module-rails | package.json |
## Unused devDependencies (2)
| Name | Location |
|:---------------------------------|:-------------|
| @types/luxon | package.json |
| @types/express-serve-static-core | package.json |
+2
View File
@@ -0,0 +1,2 @@
# Knip report
+8
View File
@@ -0,0 +1,8 @@
# Knip report
## Unused devDependencies (1)
| Name | Location |
|:-------------------|:-------------|
| @types/json-schema | package.json |
+2
View File
@@ -0,0 +1,2 @@
# Knip report
+8
View File
@@ -55,6 +55,14 @@ export interface BackstagePackageJson {
registry?: string;
};
// (undocumented)
repository?:
| string
| {
type: string;
url: string;
directory: string;
};
// (undocumented)
scripts?: {
[key: string]: string;
};
+8
View File
@@ -0,0 +1,8 @@
# Knip report
## Unused dependencies (1)
| Name | Location |
|:-------|:-------------|
| semver | package.json |
@@ -58,6 +58,14 @@ export interface BackstagePackageJson {
registry?: string;
};
repository?:
| string
| {
type: string;
url: string;
directory: string;
};
dependencies?: {
[key: string]: string;
};
+70
View File
@@ -0,0 +1,70 @@
# Knip report
## Unused dependencies (28)
| Name | Location |
|:---------------------------------|:-------------|
| util | package.json |
| glob | package.json |
| buffer | package.json |
| sucrase | package.json |
| process | package.json |
| postcss | package.json |
| esbuild | package.json |
| @swc/jest | package.json |
| @swc/core | package.json |
| @svgr/core | package.json |
| cross-fetch | package.json |
| jest-runtime | package.json |
| @swc/helpers | package.json |
| replace-in-file | package.json |
| jest-css-modules | package.json |
| @svgr/plugin-jsx | package.json |
| @svgr/plugin-svgo | package.json |
| @types/webpack-env | package.json |
| eslint-plugin-react | package.json |
| terser-webpack-plugin | package.json |
| jest-environment-jsdom | package.json |
| eslint-plugin-jsx-a11y | package.json |
| @sucrase/webpack-loader | package.json |
| eslint-plugin-react-hooks | package.json |
| eslint-plugin-deprecation | package.json |
| eslint-formatter-friendly | package.json |
| @spotify/eslint-config-react | package.json |
| @typescript-eslint/eslint-plugin | package.json |
## Unused devDependencies (13)
| Name | Location |
|:----------------------------------------|:-------------|
| del | package.json |
| ts-node | package.json |
| nodemon | package.json |
| @types/ejs | package.json |
| @types/svgo | package.json |
| @backstage/theme | package.json |
| @types/http-proxy | package.json |
| @backstage/dev-utils | package.json |
| @backstage/core-app-api | package.json |
| @backstage/core-components | package.json |
| @types/terser-webpack-plugin | package.json |
| @backstage/backend-plugin-api | package.json |
| @types/rollup-plugin-peer-deps-external | package.json |
## Referenced optional peerDependencies (4)
| Name | Location |
|:---------------------------|:-------------|
| vite | package.json |
| vite-plugin-html | package.json |
| @vitejs/plugin-react | package.json |
| vite-plugin-node-polyfills | package.json |
## Unlisted dependencies (3)
| Name | Location |
|:-------------------|:-------------------------------------------|
| prettier | src/commands/migrate/packageLintConfigs.ts |
| react-dom/client | src/lib/bundler/hasReactDomClient.ts |
| react/package.json | src/lib/bundler/server.ts |
+57 -1
View File
@@ -22,7 +22,11 @@ import {
} from '@backstage/cli-node';
import { OptionValues } from 'commander';
import fs from 'fs-extra';
import { resolve as resolvePath } from 'path';
import {
resolve as resolvePath,
join as joinPath,
relative as relativePath,
} from 'path';
import { paths } from '../../lib/paths';
/**
@@ -189,12 +193,64 @@ export function fixSideEffects(pkg: FixablePackage) {
pkg.changed = true;
}
export function createRepositoryFieldFixer() {
const rootPkg = require(paths.resolveTargetRoot('package.json'));
const rootRepoField = rootPkg.repository;
if (!rootRepoField) {
return () => {};
}
const rootType = rootRepoField.type || 'git';
const rootUrl = rootRepoField.url;
const rootDir = rootRepoField.directory || '';
return (pkg: FixablePackage) => {
const expectedPath = joinPath(
rootDir,
relativePath(paths.targetRoot, pkg.dir),
);
const repoField = pkg.packageJson.repository;
if (!repoField || typeof repoField === 'string') {
const pkgEntries = Object.entries(pkg.packageJson);
pkgEntries.splice(
// Place it just above the backstage field
pkgEntries.findIndex(([name]) => name === 'backstage'),
0,
[
'repository',
{
type: rootType,
url: rootUrl,
directory: expectedPath,
},
],
);
pkg.packageJson = Object.fromEntries(pkgEntries) as BackstagePackageJson;
pkg.changed = true;
return;
}
// If there's a type or URL mismatch, leave the field as is
if (repoField.type !== rootType || repoField.url !== rootUrl) {
return;
}
if (repoField.directory !== expectedPath) {
repoField.directory = expectedPath;
pkg.changed = true;
}
};
}
export async function command(opts: OptionValues): Promise<void> {
const packages = await readFixablePackages();
const fixRepositoryField = createRepositoryFieldFixer();
for (const pkg of packages) {
fixPackageExports(pkg);
fixSideEffects(pkg);
fixRepositoryField(pkg);
}
if (opts.check) {
+8
View File
@@ -0,0 +1,8 @@
# Knip report
## Unused devDependencies (1)
| Name | Location |
|:--------|:-------------|
| ts-node | package.json |
+2
View File
@@ -0,0 +1,2 @@
# Knip report
+2
View File
@@ -0,0 +1,2 @@
# Knip report
+20
View File
@@ -0,0 +1,20 @@
# Knip report
## Unused dependencies (1)
| Name | Location |
|:--------|:-------------|
| history | package.json |
## Unused devDependencies (7)
| Name | Location |
|:-----------------------------|:-------------|
| react-router-beta | package.json |
| react-router-stable | package.json |
| @testing-library/dom | package.json |
| react-router-dom-beta | package.json |
| react-router-dom-stable | package.json |
| @testing-library/user-event | package.json |
| @testing-library/react-hooks | package.json |
+2
View File
@@ -0,0 +1,2 @@
# Knip report
+26
View File
@@ -0,0 +1,26 @@
# Knip report
## Unused dependencies (2)
| Name | Location |
|:--------------|:-------------|
| linkifyjs | package.json |
| @date-io/core | package.json |
## Unused devDependencies (4)
| Name | Location |
|:------------------------|:-------------|
| cross-fetch | package.json |
| @testing-library/dom | package.json |
| @types/google-protobuf | package.json |
| @backstage/app-defaults | package.json |
## Unlisted dependencies (3)
| Name | Location |
|:------------------|:--------------------------------------------------------|
| csstype | src/components/Lifecycle/Lifecycle.tsx |
| copy-to-clipboard | src/components/LogViewer/RealLogViewer.test.tsx |
| copy-to-clipboard | src/components/LogViewer/useLogViewerSelection.test.tsx |
+1 -1
View File
@@ -56,7 +56,7 @@
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
"@react-hookz/web": "^23.0.0",
"@react-hookz/web": "^24.0.0",
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"@types/react-sparklines": "^1.7.0",
"@types/react-text-truncate": "^0.14.0",
+9
View File
@@ -0,0 +1,9 @@
# Knip report
## Unused devDependencies (2)
| Name | Location |
|:----------------------------|:-------------|
| @testing-library/dom | package.json |
| @testing-library/user-event | package.json |
+10
View File
@@ -0,0 +1,10 @@
# Knip report
## Unused devDependencies (3)
| Name | Location |
|:----------------------|:-------------|
| ts-node | package.json |
| nodemon | package.json |
| @types/command-exists | package.json |
+14
View File
@@ -0,0 +1,14 @@
# Knip report
## Unused dependencies (1)
| Name | Location |
|:-----------------|:-------------|
| @backstage/theme | package.json |
## Unused devDependencies (1)
| Name | Location |
|:---------------------|:-------------|
| @testing-library/dom | package.json |
+8
View File
@@ -0,0 +1,8 @@
# Knip report
## Referenced optional peerDependencies (1)
| Name | Location |
|:-----------------|:-------------|
| @playwright/test | package.json |
+5
View File
@@ -22,6 +22,11 @@
]
}
},
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/e2e-test-utils"
},
"backstage": {
"role": "node-library"
},
+10
View File
@@ -0,0 +1,10 @@
# Knip report
## Unused devDependencies (3)
| Name | Location |
|:-----------------|:-------------|
| ts-node | package.json |
| nodemon | package.json |
| @types/puppeteer | package.json |
+43
View File
@@ -71,6 +71,20 @@ export async function run() {
env: { ...process.env, CI: undefined },
});
await switchToReact17(appDir);
print(`Running 'yarn install' to install React 17`);
await runPlain(['yarn', 'install'], { cwd: appDir });
print(`Running 'yarn tsc' with React 17`);
await runPlain(['yarn', 'tsc'], { cwd: appDir });
print(`Running 'yarn test:e2e' with React 17`);
await runPlain(['yarn', 'test:e2e'], {
cwd: appDir,
env: { ...process.env, CI: undefined },
});
if (
Boolean(process.env.POSTGRES_USER) ||
Boolean(process.env.MYSQL_CONNECTION)
@@ -393,6 +407,35 @@ async function createPlugin(options: {
}
}
/**
* Switch the entire project to use React 17
*/
async function switchToReact17(appDir: string) {
const rootPkg = await fs.readJson(resolvePath(appDir, 'package.json'));
rootPkg.resolutions = {
...(rootPkg.resolutions || {}),
react: '^17.0.0',
'react-dom': '^17.0.0',
'@types/react': '^17.0.0',
'@types/react-dom': '^17.0.0',
};
await fs.writeJson(resolvePath(appDir, 'package.json'), rootPkg, {
spaces: 2,
});
await fs.writeFile(
resolvePath(appDir, 'packages/app/src/index.tsx'),
`import '@backstage/cli/asset-types';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
`,
'utf8',
);
}
/** Drops PG databases */
async function dropDB(database: string, client: string) {
try {
+2
View File
@@ -0,0 +1,2 @@
# Knip report
+9
View File
@@ -0,0 +1,9 @@
# Knip report
## Unlisted dependencies (2)
| Name | Location |
|:-------|:-------------------------------|
| estree | lib/visitImports.js |
| estree | rules/no-undeclared-imports.js |
+2
View File
@@ -0,0 +1,2 @@
# Knip report
+5
View File
@@ -9,6 +9,11 @@
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/frontend-app-api"
},
"backstage": {
"role": "web-library"
},
@@ -0,0 +1,10 @@
# Knip report
## Unused dependencies (3)
| Name | Location |
|:---------------------------|:-------------|
| lodash | package.json |
| @material-ui/core | package.json |
| @backstage/core-components | package.json |
@@ -0,0 +1,2 @@
# Knip report
@@ -9,6 +9,11 @@
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/frontend-test-utils"
},
"backstage": {
"role": "web-library"
},
@@ -0,0 +1,16 @@
# Knip report
## Unused dependencies (2)
| Name | Location |
|:----------------------------------|:-------------|
| @backstage/errors | package.json |
| @aws-sdk/credential-provider-node | package.json |
## Unused devDependencies (2)
| Name | Location |
|:-------------------------|:-------------|
| @backstage/test-utils | package.json |
| @backstage/config-loader | package.json |
@@ -0,0 +1,9 @@
# Knip report
## Unused devDependencies (2)
| Name | Location |
|:---------------------|:-------------|
| msw | package.json |
| @testing-library/dom | package.json |
+2
View File
@@ -0,0 +1,2 @@
# Knip report
@@ -0,0 +1,2 @@
# Knip report
+11
View File
@@ -15,6 +15,7 @@ Commands:
api-reports [options] [paths...]
type-deps
generate-catalog-info [options]
knip-reports [options] [paths...]
package [command]
repo [command]
help [command]
@@ -49,6 +50,16 @@ Options:
-h, --help
```
### `backstage-repo-tools knip-reports`
```
Usage: backstage-repo-tools knip-reports [options] [paths...]
Options:
--ci
-h, --help
```
### `backstage-repo-tools package`
```
+25
View File
@@ -0,0 +1,25 @@
# Knip report
## Unused dependencies (6)
| Name | Location |
|:------------------------------------|:-------------|
| glob | package.json |
| ts-node | package.json |
| is-glob | package.json |
| @stoplight/spectral-runtime | package.json |
| @stoplight/spectral-functions | package.json |
| @openapitools/openapi-generator-cli | package.json |
## Unused devDependencies (1)
| Name | Location |
|:---------------|:-------------|
| @types/is-glob | package.json |
## Referenced optional peerDependencies (1)
| Name | Location |
|:---------|:-------------|
| prettier | package.json |
@@ -16,37 +16,36 @@
import { groupBy } from 'lodash';
import {
resolve as resolvePath,
relative as relativePath,
basename,
join,
relative as relativePath,
resolve as resolvePath,
} from 'path';
import { execFile } from 'child_process';
import fs from 'fs-extra';
import {
CompilerState,
Extractor,
ExtractorConfig,
CompilerState,
ExtractorLogLevel,
ExtractorMessage,
} from '@microsoft/api-extractor';
import { Program } from 'typescript';
import {
DocNode,
IDocNodeContainerParameters,
TSDocTagSyntaxKind,
TSDocConfiguration,
Standardization,
DocBlockTag,
DocPlainText,
DocLinkTag,
DocNode,
DocPlainText,
IDocNodeContainerParameters,
Standardization,
TSDocConfiguration,
TSDocTagSyntaxKind,
} from '@microsoft/tsdoc';
import { TSDocConfigFile } from '@microsoft/tsdoc-config';
import {
ApiPackage,
ApiModel,
ApiItem,
ApiItemKind,
ApiModel,
ApiPackage,
} from '@microsoft/api-extractor-model';
import {
IMarkdownDocumenterOptions,
@@ -64,6 +63,7 @@ import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclara
import { paths as cliPaths } from '../../lib/paths';
import minimatch from 'minimatch';
import { getPackageExportNames } from '../../lib/entryPoints';
import { createBinRunner } from '../util';
const tmpDir = cliPaths.resolveTargetRoot(
'./node_modules/.cache/api-extractor',
@@ -290,10 +290,10 @@ function logApiReportInstructions() {
'*************************************************************************************',
);
console.log(
'* You have uncommitted changes to the public API of a package. *',
'* You have uncommitted changes to the public API or reports of a package. *',
);
console.log(
'* To solve this, run `yarn build:api-reports` and commit all api-report.md changes. *',
'* To solve this, run `yarn build:api-reports` and commit all md file changes. *',
);
console.log(
'*************************************************************************************',
@@ -1227,31 +1227,6 @@ export async function categorizePackageDirs(packageDirs: string[]) {
return { tsPackageDirs, cliPackageDirs };
}
function createBinRunner(cwd: string, path: string) {
return async (...command: string[]) =>
new Promise<string>((resolve, reject) => {
execFile(
'node',
[path, ...command],
{
cwd,
shell: true,
timeout: 60000,
maxBuffer: 1024 * 1024,
},
(err, stdout, stderr) => {
if (err) {
reject(new Error(`${err.message}\n${stderr}`));
} else if (stderr) {
reject(new Error(`Command printed error output: ${stderr}`));
} else {
resolve(stdout);
}
},
);
});
}
function parseHelpPage(helpPageContent: string) {
const [, usage] = helpPageContent.match(/^\s*Usage: (.*)$/im) ?? [];
const lines = helpPageContent.split(/\r?\n/);
@@ -1443,3 +1418,66 @@ export async function runCliExtraction({
}
}
}
interface KnipExtractionOptions {
packageDirs: string[];
isLocalBuild: boolean;
}
export async function runKnipReports({
packageDirs,
isLocalBuild,
}: KnipExtractionOptions) {
const knipDir = cliPaths.resolveTargetRoot('./node_modules/knip/bin/');
for (const packageDir of packageDirs) {
console.log(`## Processing ${packageDir}`);
const fullDir = cliPaths.resolveTargetRoot(packageDir);
const reportPath = resolvePath(fullDir, 'knip-report.md');
const run = createBinRunner(fullDir, '');
const report = await run(
`${knipDir}/knip.js`,
`--directory ${fullDir}`, // Run in the package directory
'--no-exit-code', // Removing this will end the process in case there are findings by knip
'--no-progress', // Remove unnecessary debugging from output
// TODO: Add more checks when dependencies start to look ok, see https://knip.dev/reference/cli#--include
'--include dependencies,unlisted',
'--reporter markdown',
);
const existingReport = await fs
.readFile(reportPath, 'utf8')
.catch(error => {
if (error.code === 'ENOENT') {
return undefined;
}
throw error;
});
if (existingReport !== report) {
if (isLocalBuild) {
console.warn(`Knip report changed for ${packageDir}`);
await fs.writeFile(reportPath, report);
} else {
logApiReportInstructions();
if (existingReport) {
console.log('');
console.log(
`The conflicting file is ${relativePath(
cliPaths.targetRoot,
reportPath,
)}, expecting the following content:`,
);
console.log('');
console.log(report);
logApiReportInstructions();
}
throw new Error(`Knip report changed for ${packageDir}, `);
}
}
}
}
@@ -20,9 +20,9 @@ import * as pathsLib from '../../lib/paths';
import {
buildDocs,
runCliExtraction,
runApiExtraction,
categorizePackageDirs,
runApiExtraction,
runCliExtraction,
} from './api-extractor';
import { buildApiReports } from './api-reports';
@@ -43,6 +43,7 @@ jest.mock('./api-extractor', () => ({
runApiExtraction: jest.fn(),
runCliExtraction: jest.fn(),
buildDocs: jest.fn(),
runKnipReports: jest.fn(),
}));
const projectPaths = pathsLib.paths;
@@ -16,11 +16,11 @@
import { OptionValues } from 'commander';
import {
createTemporaryTsConfig,
buildDocs,
categorizePackageDirs,
createTemporaryTsConfig,
runApiExtraction,
runCliExtraction,
buildDocs,
} from './api-extractor';
import { paths as cliPaths, resolvePackagePaths } from '../../lib/paths';
import { generateTypeDeclarations } from './generateTypeDeclarations';
@@ -94,6 +94,7 @@ export const buildApiReports = async (paths: string[] = [], opts: Options) => {
validateReleaseTags: opts.validateReleaseTags,
});
}
if (cliPackageDirs.length > 0) {
console.log('# Generating package CLI reports');
await runCliExtraction({
+11
View File
@@ -168,6 +168,17 @@ export function registerCommands(program: Command) {
),
),
);
program
.command('knip-reports [paths...]')
.option('--ci', 'CI run checks that there is no changes on knip reports')
.description('Generate a knip report for selected packages')
.action(
lazy(() =>
import('./knip-reports/knip-reports').then(m => m.buildKnipReports),
),
);
registerPackageCommand(program);
registerRepoCommand(program);
}

Some files were not shown because too many files have changed in this diff Show More