Merge remote-tracking branch 'upstream/master' into airbrake-api

This commit is contained in:
Karan Shah
2022-01-10 10:53:10 +00:00
192 changed files with 5406 additions and 1443 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/test-utils': patch
---
Add new `MockConfigApi` as a more discoverable and leaner method for mocking configuration.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/config': patch
---
The `ConfigReader#get` method now always returns a deep clone of the configuration data.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
fix: Register plugin to prioritise Component kind for entityRef
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-todo-backend': patch
---
Properly exported all referenced types
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Add catalog permission rules.
+28
View File
@@ -0,0 +1,28 @@
---
'@backstage/cli': minor
---
ESLint upgraded to version 8 and all it's plugins updated to newest version.
If you use any custom plugins for ESLint please check compatibility.
```diff
- "@typescript-eslint/eslint-plugin": "^v4.33.0",
- "@typescript-eslint/parser": "^v4.28.3",
+ "@typescript-eslint/eslint-plugin": "^5.9.0",
+ "@typescript-eslint/parser": "^5.9.0",
- "eslint": "^7.30.0",
+ "eslint": "^8.6.0",
- "eslint-plugin-import": "^2.20.2",
- "eslint-plugin-jest": "^24.1.0",
- "eslint-plugin-jsx-a11y": "^6.2.1",
+ "eslint-plugin-import": "^2.25.4",
+ "eslint-plugin-jest": "^25.3.4",
+ "eslint-plugin-jsx-a11y": "^6.5.1",
- "eslint-plugin-react": "^7.12.4",
- "eslint-plugin-react-hooks": "^4.0.0",
+ "eslint-plugin-react": "^7.28.0",
+ "eslint-plugin-react-hooks": "^4.3.0",
```
Please consult changelogs from packages if you find any problems.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-graphql': patch
---
Bump graphql versions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-ldap': patch
---
Make sure to avoid accidental data sharing / mutation of `set` values
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-permission-node': patch
---
Add helpers for creating PermissionRules with inferred types
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/core-plugin-api': minor
---
Removed previously deprecated exports: `PluginHooks`, `PluginOutput`, and `FeatureFlagOutput`.
The deprecated `register` method of `PluginConfig` has been removed, as well as the deprecated `output` method of `BackstagePlugin`.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-catalog-backend-module-ldap': patch
'@backstage/plugin-catalog-backend-module-msgraph': patch
---
Clean up API report
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-home': patch
---
Adds two new home components - CompanyLogo and Toolkit.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Switch Webpack minification to use `esbuild` instead of `terser`.
+60
View File
@@ -0,0 +1,60 @@
---
'@backstage/create-app': patch
---
Add permissions to create-app's PluginEnvironment
`CatalogEnvironment` now has a `permissions` field, which means that a permission client must now be provided as part of `PluginEnvironment`. To apply these changes to an existing app, add the following to the `makeCreateEnv` function in `packages/backend/src/index.ts`:
```diff
// packages/backend/src/index.ts
+ import { ServerPermissionClient } from '@backstage/plugin-permission-node';
function makeCreateEnv(config: Config) {
...
+ const permissions = ServerPerimssionClient.fromConfig(config, {
+ discovery,
+ tokenManager,
+ });
root.info(`Created UrlReader ${reader}`);
return (plugin: string): PluginEnvironment => {
...
return {
logger,
cache,
database,
config,
reader,
discovery,
tokenManager,
scheduler,
+ permissions,
};
}
}
```
And add a permissions field to the `PluginEnvironment` type in `packages/backend/src/types.ts`:
```diff
// packages/backend/src/types.ts
+ import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
export type PluginEnvironment = {
...
+ permissions: PermissionAuthorizer;
};
```
[`@backstage/plugin-permission-common`](https://www.npmjs.com/package/@backstage/plugin-permission-common) and [`@backstage/plugin-permission-node`](https://www.npmjs.com/package/@backstage/plugin-permission-node) will need to be installed as dependencies:
```diff
// packages/backend/package.json
+ "@backstage/plugin-permission-common": "...",
+ "@backstage/plugin-permission-node": "...",
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
bump `logform` to use fixed version of `color` dependency
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Updated `ResponseErrorPanel` to not use the deprecated `data` property of `ResponseError`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Update `config/eslint.js` to forbid imports of `@material-ui/icons/` as well.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': patch
---
Removed `@backstage/test-utils` dependency.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Add apply-conditions endpoint for evaluating conditional permissions in catalog backend.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-app-api': minor
---
Removed previously deprecated `ApiRegistry` export.
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Fix an issue where the TechDocs sidebar is hidden when the Backstage sidebar is pinned at smaller screen sizes
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-catalog-backend': minor
---
In order to integrate the permissions system with the refresh endpoint in catalog-backend, a new AuthorizedRefreshService was created as a thin wrapper around the existing refresh service which performs authorization and handles the case when authorization is denied. In order to instantiate AuthorizedRefreshService, a permission client is required, which was added as a new field to `CatalogEnvironment`.
The new `permissions` field in `CatalogEnvironment` should already receive the permission client from the `PluginEnvrionment`, so there should be no changes required to the catalog backend setup. See [the create-app changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md) for more details.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
Clean up API reports
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/errors': minor
---
Removed the deprecated exports `ErrorResponse` and `parseErrorResponse`.
Removed the deprecated `constructor` and the deprecated `data` property of `ResponseError`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Exclude the AWS session token from credential validation, because it's not necessary in this context.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-tech-insights': patch
---
fix React warning because of missing `key` prop
+2 -2
View File
@@ -5,7 +5,7 @@ updates:
schedule:
interval: daily
time: '04:00'
open-pull-requests-limit: 5
open-pull-requests-limit: 10
labels:
- dependencies
- package-ecosystem: npm
@@ -13,6 +13,6 @@ updates:
schedule:
interval: daily
time: '04:00'
open-pull-requests-limit: 2
open-pull-requests-limit: 5
labels:
- dependencies
@@ -4,6 +4,14 @@ on:
paths:
- '.github/workflows/chromatic-storybook-test.yml'
- 'packages/storybook/**'
- 'packages/config/src/**'
- 'packages/theme/src/**'
- 'packages/types/src/**'
- 'packages/errors/src/**'
- 'packages/version-bridge/src/**'
- 'packages/test-utils/src/**'
- 'packages/core-app-api/src/**'
- 'packages/core-plugin-api/src/**'
- 'packages/core-components/src/**'
- '**/*.stories.tsx'
+3
View File
@@ -136,3 +136,6 @@ site
# e2e tests
cypress/cypress/*
# Possible leftover from build:api-reports
tsconfig.tmp.json
+3 -2
View File
@@ -77,5 +77,6 @@
| [Mox Bank](https://www.mox.com/) | [Nick Laqua](https://github.com/nick-laqua-dragon), [Gauthier Roebroeck](https://github.com/gauthier-roebroeck-mox) | "Single pane of glass" developer portal for providing a best-in-class developer experience to our product teams and making Mox the best tech environment in Hongkong 🥰🚀 |
| [Keyloop](https://www.keyloop.com/) | [Andre Wanlin](https://github.com/awanlin) | Future-motive Developer Portal to help our teams create technology to make everything about buying and owning a car better. 🚗 |
| [Simply Business](https://sbtech.simplybusiness.co.uk/) | [@addersuk](https://github.com/addersuk), [@LightningStairs](https://github.com/LightningStairs), [@punitcse](https://github.com/punitcse), [@moltenice](https://github.com/moltenice) | Central developer portal to access everything a developer needs such as docs, internal service catalog, and the ability to quickly create a new service from a template. Internally developed Backstage plugins allow us to customise the experience to how we work. |
| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding |
| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. |
| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding |
| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. |
| [EF Education First](https://www.ef.com) | [Daan Boerlage](https://github.com/runebaas), [Rafał Nowosielski](https://github.com/rnowosielski) | Our developer portal - primarily used for cataloging and scaffolding with the ambition to expand with more feature adoptions over time |
@@ -1 +0,0 @@
<mxfile host="Chrome" modified="2021-12-30T23:10:43.723Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36" etag="XmQE_awyVPeS9B3bWnM_" version="15.5.6" type="device"><diagram id="M4OCM2KiCGRnt6vHj1W_" name="Page-1">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</diagram></mxfile>
@@ -0,0 +1,746 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1261px" height="642px" viewBox="-0.5 -0.5 1261 642" content="&lt;mxfile&gt;&lt;diagram id=&quot;M4OCM2KiCGRnt6vHj1W_&quot; name=&quot;Page-1&quot;&gt;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&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(255, 255, 255);">
<defs/>
<g>
<rect x="0" y="0" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="all"/>
<rect x="180" y="500" width="100" height="130" fill="none" stroke="#000000" pointer-events="all"/>
<path d="M 160 245 L 253.88 245" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 258.88 245 L 253.88 246.67 L 253.88 243.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 120 230 Q 120 230 120 136.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 120 131.12 L 121.67 136.12 L 118.33 136.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 120 260 Q 120 305 143.88 305" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 148.88 305 L 143.88 306.67 L 143.88 303.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 140 230 Q 140 137.5 243.88 137.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 248.88 137.5 L 243.88 139.17 L 243.88 135.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="80" y="230" width="80" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 70px; height: 1px; padding-top: 245px; margin-left: 90px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
app
</div>
</div>
</div>
</foreignObject>
<text x="90" y="249" fill="#000000" font-family="Helvetica" font-size="12px">
app
</text>
</switch>
</g>
<path d="M 120 390 Q 120 345 143.88 345" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 148.88 345 L 143.88 346.67 L 143.88 343.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 160 405 L 253.88 405" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 258.88 405 L 253.88 406.67 L 253.88 403.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 140 420 Q 140 450 347.5 450 Q 555 450 555 473.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 555 478.88 L 553.33 473.88 L 556.67 473.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="80" y="390" width="80" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 70px; height: 1px; padding-top: 405px; margin-left: 90px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
backend
</div>
</div>
</div>
</foreignObject>
<text x="90" y="409" fill="#000000" font-family="Helvetica" font-size="12px">
backend
</text>
</switch>
</g>
<path d="M 387.5 230 Q 387.5 205 378.75 205 Q 370 205 370 186.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 370 181.12 L 371.67 186.12 L 368.33 186.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 430 245 Q 475 245 475 266.25 Q 475 287.5 513.88 287.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 518.88 287.5 L 513.88 289.17 L 513.88 285.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 430 237.5 Q 585 237.5 585 233.75 Q 585 230 690 230 Q 795 230 795 243.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 795 248.88 L 793.33 243.88 L 796.67 243.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="260" y="230" width="170" height="30" fill="#dae8fc" stroke="#6c8ebf" stroke-width="3" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 160px; height: 1px; padding-top: 245px; margin-left: 270px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;
</div>
</div>
</div>
</foreignObject>
<text x="270" y="249" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;
</text>
</switch>
</g>
<path d="M 387.5 420 Q 387.5 555 493.88 555" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 498.88 555 L 493.88 556.67 L 493.88 553.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 430 412.5 Q 585 412.5 585 416.25 Q 585 420 690 420 Q 795 420 795 406.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 795 401.12 L 796.67 406.12 L 793.33 406.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 430 405 Q 475 405 475 383.75 Q 475 362.5 513.88 362.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 518.88 362.5 L 513.88 364.17 L 513.88 360.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="260" y="390" width="170" height="30" fill="#fff2cc" stroke="#d6b656" stroke-width="3" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 160px; height: 1px; padding-top: 405px; margin-left: 270px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-backend
</div>
</div>
</div>
</foreignObject>
<text x="270" y="409" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-backend
</text>
</switch>
</g>
<path d="M 500 480 L 500 480 L 720 480 L 720 480" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 500 480 L 500 630 L 720 630 L 720 480" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 500 480 L 720 480" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="609.5" y="497.5">
Backend Libraries
</text>
</g>
<rect x="520" y="510" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 525px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/backend-common
</div>
</div>
</div>
</foreignObject>
<text x="530" y="529" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/backend-common
</text>
</switch>
</g>
<rect x="520" y="550" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 565px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/backend-test-utils
</div>
</div>
</div>
</foreignObject>
<text x="530" y="569" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/backend-test-utils
</text>
</switch>
</g>
<rect x="520" y="590" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 605px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/backend-tasks
</div>
</div>
</div>
</foreignObject>
<text x="530" y="609" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/backend-tasks
</text>
</switch>
</g>
<path d="M 1030 250 L 1030 250 L 1250 250 L 1250 250" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1030 250 L 1030 520 L 1250 520 L 1250 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1030 250 L 1250 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="1139.5" y="267.5">
Common Libraries
</text>
</g>
<rect x="1050" y="440" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 455px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/catalog-client
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="459" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/catalog-client
</text>
</switch>
</g>
<rect x="1050" y="280" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 295px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/types
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="299" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/types
</text>
</switch>
</g>
<rect x="1050" y="320" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 335px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/config
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="339" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/config
</text>
</switch>
</g>
<rect x="1050" y="360" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 375px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/errors
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="379" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/errors
</text>
</switch>
</g>
<rect x="1050" y="400" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 415px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/catalog-model
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="419" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/catalog-model
</text>
</switch>
</g>
<rect x="1050" y="480" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 495px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/integration
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="499" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/integration
</text>
</switch>
</g>
<path d="M 10 20 L 10 20 L 230 20 L 230 20" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 10 20 L 10 130 L 230 130 L 230 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 10 20 L 230 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="119.5" y="37.5">
Frontend App Core
</text>
</g>
<rect x="30" y="50" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 65px; margin-left: 40px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/core-app-api
</div>
</div>
</div>
</foreignObject>
<text x="40" y="69" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/core-app-api
</text>
</switch>
</g>
<rect x="30" y="90" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 105px; margin-left: 40px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/app-defaults
</div>
</div>
</div>
</foreignObject>
<text x="40" y="109" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/app-defaults
</text>
</switch>
</g>
<path d="M 988 305 Q 1010 305 1010 345 Q 1010 385 1023.88 385" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1028.88 385 L 1023.88 386.67 L 1023.88 383.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 988 305 Q 1010 305 1010 250 Q 1010 195 1023.88 195" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1028.88 195 L 1023.88 196.67 L 1023.88 193.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 990 10 L 985 10 Q 980 10 980 20 L 980 295 Q 980 305 975 305 L 972.5 305 Q 970 305 975 305 L 977.5 305 Q 980 305 980 315 L 980 590 Q 980 600 985 600 L 990 600" fill="none" stroke="#000000" stroke-miterlimit="10" transform="translate(980,0)scale(-1,1)translate(-980,0)" pointer-events="none"/>
<path d="M 430 345 Q 475 345 475 353.75 Q 475 362.5 513.88 362.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 518.88 362.5 L 513.88 364.17 L 513.88 360.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 430 352.5 Q 465 352.5 465 435 Q 465 517.5 493.88 517.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 498.88 517.5 L 493.88 519.17 L 493.88 515.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="150" y="330" width="280" height="30" fill="#fff2cc" stroke="#d6b656" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 270px; height: 1px; padding-top: 345px; margin-left: 160px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-backend-module-&lt;module-id&gt;
</div>
</div>
</div>
</foreignObject>
<text x="160" y="349" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-backend-module-&lt;module-id&gt;
</text>
</switch>
</g>
<path d="M 430 305 Q 475 305 475 296.25 Q 475 287.5 513.88 287.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 518.88 287.5 L 513.88 289.17 L 513.88 285.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 430 297.5 Q 464.08 297.5 464.08 186.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 464.08 181.12 L 465.75 186.12 L 462.41 186.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="150" y="290" width="280" height="30" fill="#dae8fc" stroke="#6c8ebf" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 270px; height: 1px; padding-top: 305px; margin-left: 160px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-module-&lt;module-id&gt;
</div>
</div>
</div>
</foreignObject>
<text x="160" y="309" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-module-&lt;module-id&gt;
</text>
</switch>
</g>
<path d="M 1030 160 L 1030 160 L 1250 160 L 1250 160" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1030 160 L 1030 230 L 1250 230 L 1250 160" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1030 160 L 1250 160" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="1139.5" y="177.5">
Common Tooling
</text>
</g>
<rect x="1050" y="190" width="180" height="30" fill="#e1d5e7" stroke="#9673a6" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 205px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/cli
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="209" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/cli
</text>
</switch>
</g>
<path d="M 850 400 Q 850 555 726.12 555" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 721.12 555 L 726.12 553.33 L 726.12 556.67 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 850 250 Q 850 95 736.12 95" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 731.12 95 L 736.12 93.33 L 736.12 96.67 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 740 250 L 740 250 L 960 250 L 960 250" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 740 250 L 740 400 L 960 400 L 960 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 740 250 L 960 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="849.5" y="267.5">
External Plugin Libraries
</text>
</g>
<rect x="750" y="280" width="200" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 190px; height: 1px; padding-top: 295px; margin-left: 760px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
plugin-&lt;other-plugin-id&gt;-react
</div>
</div>
</div>
</foreignObject>
<text x="760" y="299" fill="#000000" font-family="Helvetica" font-size="12px">
plugin-&lt;other-plugin-id&gt;-react
</text>
</switch>
</g>
<rect x="750" y="320" width="200" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 190px; height: 1px; padding-top: 335px; margin-left: 760px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
plugin-&lt;other-plugin-id&gt;-common
</div>
</div>
</div>
</foreignObject>
<text x="760" y="339" fill="#000000" font-family="Helvetica" font-size="12px">
plugin-&lt;other-plugin-id&gt;-common
</text>
</switch>
</g>
<rect x="750" y="360" width="200" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 190px; height: 1px; padding-top: 375px; margin-left: 760px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
plugin-&lt;other-plugin-id&gt;-node
</div>
</div>
</div>
</foreignObject>
<text x="760" y="379" fill="#000000" font-family="Helvetica" font-size="12px">
plugin-&lt;other-plugin-id&gt;-node
</text>
</switch>
</g>
<path d="M 700 325 Q 700 325 733.88 325" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 738.88 325 L 733.88 326.67 L 733.88 323.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 610 400 Q 610 400 610 473.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 610 478.88 L 608.33 473.88 L 611.67 473.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 610 250 Q 610 250 610 186.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 610 181.12 L 611.67 186.12 L 608.33 186.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 520 250 L 520 250 L 700 250 L 700 250" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 520 250 L 520 400 L 700 400 L 700 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 520 250 L 700 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="609.5" y="267.5">
Plugin Libraries
</text>
</g>
<rect x="530" y="280" width="160" height="30" fill="#dae8fc" stroke="#6c8ebf" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 150px; height: 1px; padding-top: 295px; margin-left: 540px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-react
</div>
</div>
</div>
</foreignObject>
<text x="540" y="299" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-react
</text>
</switch>
</g>
<rect x="530" y="320" width="160" height="30" fill="#d5e8d4" stroke="#82b366" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 150px; height: 1px; padding-top: 335px; margin-left: 540px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-common
</div>
</div>
</div>
</foreignObject>
<text x="540" y="339" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-common
</text>
</switch>
</g>
<rect x="530" y="360" width="160" height="30" fill="#fff2cc" stroke="#d6b656" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 150px; height: 1px; padding-top: 375px; margin-left: 540px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-node
</div>
</div>
</div>
</foreignObject>
<text x="540" y="379" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-node
</text>
</switch>
</g>
<path d="M 250 10 L 250 10 L 730 10 L 730 10" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/>
<path d="M 250 10 L 250 180 L 730 180 L 730 10" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/>
<path d="M 250 10 L 730 10" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/>
<path d="M 260 20 L 260 20 L 480 20 L 480 20" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 260 20 L 260 170 L 480 170 L 480 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 260 20 L 480 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="369.5" y="37.5">
Frontend Plugin Core
</text>
</g>
<rect x="280" y="50" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 65px; margin-left: 290px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/core-plugin-api
</div>
</div>
</div>
</foreignObject>
<text x="290" y="69" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/core-plugin-api
</text>
</switch>
</g>
<rect x="280" y="90" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 105px; margin-left: 290px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/test-utils
</div>
</div>
</div>
</foreignObject>
<text x="290" y="109" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/test-utils
</text>
</switch>
</g>
<rect x="280" y="130" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 145px; margin-left: 290px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/dev-utils
</div>
</div>
</div>
</foreignObject>
<text x="290" y="149" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/dev-utils
</text>
</switch>
</g>
<path d="M 500 20 L 500 20 L 720 20 L 720 20" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 500 20 L 500 170 L 720 170 L 720 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 500 20 L 720 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="609.5" y="37.5">
Frontend Libraries
</text>
</g>
<rect x="520" y="130" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 145px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/integration-react
</div>
</div>
</div>
</foreignObject>
<text x="530" y="149" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/integration-react
</text>
</switch>
</g>
<rect x="520" y="50" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 65px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/core-components
</div>
</div>
</div>
</foreignObject>
<text x="530" y="69" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/core-components
</text>
</switch>
</g>
<rect x="520" y="90" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 105px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/theme
</div>
</div>
</div>
</foreignObject>
<text x="530" y="109" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/theme
</text>
</switch>
</g>
<rect x="10" y="500" width="160" height="130" fill="none" stroke="#000000" pointer-events="none"/>
<rect x="20" y="510" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<rect x="20" y="570" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<rect x="20" y="540" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 520px; margin-left: 50px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
Frontend Package
</div>
</div>
</div>
</foreignObject>
<text x="50" y="524" fill="#000000" font-family="Helvetica" font-size="12px">
Frontend Package
</text>
</switch>
</g>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 550px; margin-left: 50px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
Isomorphic Package
</div>
</div>
</div>
</foreignObject>
<text x="50" y="554" fill="#000000" font-family="Helvetica" font-size="12px">
Isomorphic Package
</text>
</switch>
</g>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 580px; margin-left: 50px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
Backend Package
</div>
</div>
</div>
</foreignObject>
<text x="50" y="584" fill="#000000" font-family="Helvetica" font-size="12px">
Backend Package
</text>
</switch>
</g>
<rect x="20" y="600" width="20" height="20" fill="#e1d5e7" stroke="#9673a6" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 610px; margin-left: 50px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
CLI Package
</div>
</div>
</div>
</foreignObject>
<text x="50" y="614" fill="#000000" font-family="Helvetica" font-size="12px">
CLI Package
</text>
</switch>
</g>
<rect x="190" y="540" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<rect x="190" y="600" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<rect x="190" y="570" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<rect x="250" y="540" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<path d="M 210 550 Q 210 550 243.88 550" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 550 L 243.88 551.67 L 243.88 548.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="250" y="600" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<path d="M 210 610 Q 210 610 243.88 610" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 610 L 243.88 611.67 L 243.88 608.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="250" y="570" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<path d="M 210 550 Q 230 550 230 562.5 Q 230 575 243.88 575" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 575 L 243.88 576.67 L 243.88 573.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 210 605 Q 230 605 230 595 Q 230 585 243.88 585" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 585 L 243.88 586.67 L 243.88 583.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 210 580 Q 210 580 243.88 580" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 580 L 243.88 581.67 L 243.88 578.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 90px; height: 1px; padding-top: 520px; margin-left: 190px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
Compatibility
</div>
</div>
</div>
</foreignObject>
<text x="190" y="524" fill="#000000" font-family="Helvetica" font-size="12px">
Compatibility
</text>
</switch>
</g>
<path d="M 251 589 L 251 570 L 270 589 Z" fill="#e1d5e7" stroke="#9673a6" stroke-miterlimit="10" transform="rotate(-180,260.5,579.5)" pointer-events="none"/>
<rect x="0" y="630" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="none"/>
<rect x="1250" y="630" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="none"/>
<rect x="1250" y="0" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="none"/>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

+38
View File
@@ -50,3 +50,41 @@ The Bitbucket provider is a structure with two configuration keys:
To add the provider to the frontend, add the `bitbucketAuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
## Using Bitbucket for sign-in
In order to use the Bitbucket provider for sign-in, you must configure it with a
`signIn.resolver`. See the
[Sign-In Resolver documentation](../identity-resolver.md) for more details on
how this is done. Note that for the Bitbucket provider, you'll want to use
`bitbucket` as the provider ID, and `createBitbucketProvider` for the provider
factory.
The `@backstage/plugin-auth-backend` plugin also comes with two built-in
resolves that can be used if desired. The first one is the
`bitbucketUsernameSignInResolver`, which identifies users by matching their
Bitbucket username to `bitbucket.org/username` annotations of `User` entities in
the catalog. Note that you must populate your catalog with matching entities or
users will not be able to sign in.
The second resolver is the `bitbucketUsernameSignInResolver`, which works the
same way, but uses the Bitbucket user ID instead, and matches on the
`bitbucket.org/user-id` annotation.
The following is an example of how to use one of the built-in resolvers:
```ts
import {
createBitbucketProvider,
bitbucketUsernameSignInResolver,
} from '@backstage/plugin-auth-backend';
// ...
providerFactories: {
bitbucket: createBitbucketProvider({
signIn: {
resolver: bitbucketUsernameSignInResolver,
},
}),
},
```
+34
View File
@@ -97,3 +97,37 @@ of the `SearchType` component.
...
</Paper>
```
## How to limit what can be searched in the Software Catalog
The Software Catalog includes a wealth of information about the components,
systems, groups, users, and other aspects of your software ecosystem. However,
you may not always want _every_ aspect to appear when a user searches the
catalog. Examples include:
- Entities of kind `Location`, which are often not useful to Backstage users.
- Entities of kind `User` or `Group`, if you'd prefer that users and groups be
exposed to search in a different way (or not at all).
It's possible to write your own [Collator](./concepts.md#collators) to control
exactly what's available to search, (or a [Decorator](./concepts.md#decorators)
to filter things out here and there), but the `DefaultCatalogCollator` that's
provided by `@backstage/plugin-catalog-backend` offers some configuration too!
```diff
// packages/backend/src/plugins/search.ts
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: DefaultCatalogCollator.fromConfig(config, {
discovery,
tokenManager,
+ filter: {
+ kind: ['API', 'Component', 'Domain', 'Group', 'System', 'User'],
+ },
}),
});
```
As shown above, you can add a catalog entity filter to narrow down what catalog
entities are indexed by the search engine.
+1 -1
View File
@@ -230,7 +230,7 @@ You can do so by including the following lines in the last step of your
```Dockerfile
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip3 install mkdocs-techdocs-core==0.0.16
RUN pip3 install mkdocs-techdocs-core==0.2.2
```
Please be aware that the version requirement could change, you need to check our
+70
View File
@@ -253,3 +253,73 @@ const LogoFull = () => {
return <img src={MyCustomLogoFull} />;
};
```
## Custom Homepage
In addition to a custom theme, a custom logo, you can also customize the
homepage of your app. To do that we need to go through a few steps.
### Setting up the Home Page
1. Create a Home Page Component that will be used for composition.
`packages/app/src/components/home/HomePage.tsx`
```tsx
import React from 'react';
export const HomePage = () => {
return {
/* TODO: Compose a Home Page here */
};
};
```
2. Add a route where the homepage will live, presumably `/`.
`packages/app/src/App.tsx`
```tsx
import { HomepageCompositionRoot } from '@backstage/plugin-home';
import { HomePage } from './components/home/HomePage';
// ...
<Route path="/" element={<HomepageCompositionRoot />}>
<HomePage />
</Route>;
// ...
```
### Composing your Home Page
Composing a Home Page is no different from creating a regular React Component,
i.e. the App Integrator is free to include whatever content they like. However,
there are components developed with the Home Page in mind. If you are looking
for components to use when composing your homepage, you can take a look at the
[collection of Homepage components](https://backstage.io/?path=/story/plugins-home-components)
in storybook. If you don't find a component that suits your needs but want to
contribute, check the
[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing).
!!! tip If you want to use one of the available homepage templates you can find
the
[templates](https://backstage.io/storybook/?path=/story/plugins-home-templates)
in the storybook under the "Home" plugin. And if you would like to contribute a
template, please see the
[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing)
```tsx
import React from 'react';
import Grid from '@material-ui/core/Grid';
import { HomePageCompanyLogo } from '@backstage/plugin-home';
export const HomePage = () => {
return (
<Grid container spacing={3}>
<Grid item xs={12} md={4}>
<HomePageCompanyLogo className={container} />
</Grid>
</Grid>
);
};
```
+1 -7
View File
@@ -179,13 +179,7 @@ Surrounding the plugin are different package groups which are the different
possible interface points of the plugin. Note that not all library package lists
are complete as packages have been omitted for brevity.
![Package architecture](../assets/architecture-overview/package-architecture.png)
<!--
NOTE: The diagram is exported from /docs/assets/architecture-overview/package-architecture.drawio
It can be edited using https://www.diagrams.net/
Export it as a PNG with 10px margin without transparent background
-->
![Package architecture](../assets/architecture-overview/package-architecture.drawio.svg)
### Overview
+2 -2
View File
@@ -9,7 +9,7 @@
"start": "yarn workspace example-app start",
"start-backend": "yarn workspace example-backend start",
"build": "lerna run build",
"build:api-reports": "yarn tsc:full && yarn build:api-reports:only",
"build:api-reports": "yarn build:api-reports:only --tsc",
"build:api-reports:only": "ts-node -T -P scripts/tsconfig.json scripts/api-extractor.ts",
"build:api-docs": "yarn build:api-reports --docs",
"tsc": "tsc",
@@ -68,7 +68,7 @@
"@types/webpack": "^5.28.0",
"command-exists": "^1.2.9",
"cross-env": "^7.0.0",
"concurrently": "^6.0.0",
"concurrently": "^7.0.0",
"eslint-plugin-notice": "^0.9.10",
"fs-extra": "9.1.0",
"husky": "^6.0.0",
+23 -30
View File
@@ -34,9 +34,7 @@ import { Server } from 'http';
import * as winston from 'winston';
import { Writable } from 'stream';
// Warning: (ae-missing-release-tag) "AwsS3UrlReader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public
export class AwsS3UrlReader implements UrlReader {
constructor(
integration: AwsS3Integration,
@@ -59,7 +57,7 @@ export class AwsS3UrlReader implements UrlReader {
toString(): string;
}
// @public (undocumented)
// @public
export class AzureUrlReader implements UrlReader {
constructor(
integration: AzureIntegration,
@@ -114,12 +112,12 @@ export interface CacheClient {
): Promise<void>;
}
// @public (undocumented)
// @public
export type CacheClientOptions = {
defaultTtl?: number;
};
// @public (undocumented)
// @public
export type CacheClientSetOptions = {
ttl?: number;
};
@@ -133,18 +131,17 @@ export class CacheManager {
): CacheManager;
}
// @public (undocumented)
// @public
export type CacheManagerOptions = {
logger?: Logger_2;
onError?: (err: Error) => void;
};
// @public (undocumented)
// @public
export const coloredFormat: winston.Logform.Format;
// @public (undocumented)
// @public
export interface ContainerRunner {
// (undocumented)
runContainer(opts: RunContainerOptions): Promise<void>;
}
@@ -157,7 +154,7 @@ export function createDatabaseClient(
overrides?: Partial<Knex.Config>,
): Knex<any, unknown[]>;
// @public (undocumented)
// @public
export function createRootLogger(
options?: winston.LoggerOptions,
env?: NodeJS.ProcessEnv,
@@ -166,14 +163,14 @@ export function createRootLogger(
// @public
export function createServiceBuilder(_module: NodeModule): ServiceBuilder;
// @public (undocumented)
// @public
export function createStatusCheckRouter(options: {
logger: Logger_2;
path?: string;
statusCheck?: StatusCheck;
}): Promise<express.Router>;
// @public (undocumented)
// @public
export class DatabaseManager {
forPlugin(pluginId: string): PluginDatabaseManager;
static fromConfig(
@@ -187,7 +184,7 @@ export type DatabaseManagerOptions = {
migrations?: PluginDatabaseManager['migrations'];
};
// @public (undocumented)
// @public
export class DockerContainerRunner implements ContainerRunner {
constructor(options: { dockerClient: Docker });
// (undocumented)
@@ -205,7 +202,7 @@ export function errorHandler(
options?: ErrorHandlerOptions,
): ErrorRequestHandler;
// @public (undocumented)
// @public
export type ErrorHandlerOptions = {
showStackTraces?: boolean;
logger?: Logger_2;
@@ -218,13 +215,13 @@ export type FromReadableArrayOptions = Array<{
path: string;
}>;
// @public (undocumented)
// @public
export function getRootLogger(): winston.Logger;
// @public
export function getVoidLogger(): winston.Logger;
// @public (undocumented)
// @public
export class Git {
// (undocumented)
add(options: { dir: string; filepath: string }): Promise<void>;
@@ -310,7 +307,7 @@ export class GithubUrlReader implements UrlReader {
toString(): string;
}
// @public (undocumented)
// @public
export class GitlabUrlReader implements UrlReader {
constructor(
integration: GitLabIntegration,
@@ -398,7 +395,7 @@ export type ReadTreeResponseDirOptions = {
targetDir?: string;
};
// @public (undocumented)
// @public
export interface ReadTreeResponseFactory {
// (undocumented)
fromReadableArray(
@@ -448,7 +445,7 @@ export type ReadUrlResponse = {
// @public
export function requestLoggingHandler(logger?: Logger_2): RequestHandler;
// @public (undocumented)
// @public
export type RequestLoggingHandlerFactory = (
logger?: Logger_2,
) => RequestHandler;
@@ -459,7 +456,7 @@ export function resolvePackagePath(name: string, ...paths: string[]): string;
// @public
export function resolveSafeChildPath(base: string, path: string): string;
// @public (undocumented)
// @public
export type RunContainerOptions = {
imageName: string;
command?: string | string[];
@@ -508,7 +505,7 @@ export class ServerTokenManager implements TokenManager {
static noop(): TokenManager;
}
// @public (undocumented)
// @public
export type ServiceBuilder = {
loadConfig(config: Config): ServiceBuilder;
setPort(port: number): ServiceBuilder;
@@ -534,7 +531,7 @@ export type ServiceBuilder = {
start(): Promise<Server>;
};
// @public (undocumented)
// @public
export function setRootLogger(newLogger: winston.Logger): void;
// @public @deprecated
@@ -554,7 +551,7 @@ export class SingleHostDiscovery implements PluginEndpointDiscovery {
getExternalBaseUrl(pluginId: string): Promise<string>;
}
// @public (undocumented)
// @public
export type StatusCheck = () => Promise<any>;
// @public
@@ -562,7 +559,7 @@ export function statusCheckHandler(
options?: StatusCheckHandlerOptions,
): Promise<RequestHandler>;
// @public (undocumented)
// @public
export interface StatusCheckHandlerOptions {
statusCheck?: StatusCheck;
}
@@ -597,7 +594,7 @@ export class UrlReaders {
static default(options: UrlReadersOptions): UrlReader;
}
// @public (undocumented)
// @public
export type UrlReadersOptions = {
config: Config;
logger: Logger_2;
@@ -612,8 +609,4 @@ export function useHotCleanup(
// @public
export function useHotMemoize<T>(_module: NodeModule, valueFactory: () => T): T;
// Warnings were encountered during analysis:
//
// src/database/types.d.ts:23:12 - (tsdoc-undefined-tag) The TSDoc tag "@default" is not defined in this configuration
```
+1 -1
View File
@@ -58,7 +58,7 @@
"keyv-memcache": "^1.2.5",
"knex": "^0.95.1",
"lodash": "^4.17.21",
"logform": "^2.1.1",
"logform": "^2.3.2",
"minimatch": "^3.0.4",
"minimist": "^1.2.5",
"morgan": "^1.10.0",
+5 -1
View File
@@ -22,7 +22,11 @@ type CacheClientArgs = {
client: Keyv;
};
/** @public */
/**
* Options passed to {@link CacheClient.set}.
*
* @public
*/
export type CacheClientSetOptions = {
/**
* Optional TTL in milliseconds. Defaults to the TTL provided when the client
+4 -3
View File
@@ -55,8 +55,8 @@ export class CacheManager {
private readonly errorHandler: CacheManagerOptions['onError'];
/**
* Creates a new CacheManager instance by reading from the `backend` config
* section, specifically the `.cache` key.
* Creates a new {@link CacheManager} instance by reading from the `backend`
* config section, specifically the `.cache` key.
*
* @param config - The loaded application configuration.
*/
@@ -93,7 +93,8 @@ export class CacheManager {
/**
* Generates a PluginCacheManager for consumption by plugins.
*
* @param pluginId - The plugin that the cache manager should be created for. Plugin names should be unique.
* @param pluginId - The plugin that the cache manager should be created for.
* Plugin names should be unique.
*/
forPlugin(pluginId: string): PluginCacheManager {
return {
+17 -7
View File
@@ -17,7 +17,11 @@
import { Logger } from 'winston';
import { CacheClient } from './CacheClient';
/** @public */
/**
* Options given when constructing a {@link CacheClient}.
*
* @public
*/
export type CacheClientOptions = {
/**
* An optional default TTL (in milliseconds) to be set when getting a client
@@ -27,7 +31,11 @@ export type CacheClientOptions = {
defaultTtl?: number;
};
/** @public */
/**
* Options given when constructing a {@link CacheManager}.
*
* @public
*/
export type CacheManagerOptions = {
/**
* An optional logger for use by the PluginCacheManager.
@@ -42,17 +50,19 @@ export type CacheManagerOptions = {
};
/**
* The PluginCacheManager manages access to cache stores that Plugins get.
* Manages access to cache stores that plugins get.
*
* @public
*/
export type PluginCacheManager = {
/**
* getClient provides backend plugins cache connections for itself.
* Provides backend plugins cache connections for themselves.
*
* The purpose of this method is to allow plugins to get isolated data
* stores so that plugins are discouraged from cache-level integration
* and/or cache key collisions.
* @remarks
*
* The purpose of this method is to allow plugins to get isolated data stores
* so that plugins are discouraged from cache-level integration and/or cache
* key collisions.
*/
getClient: (options?: CacheClientOptions) => CacheClient;
};
@@ -38,7 +38,7 @@ function pluginPath(pluginId: string): string {
}
/**
* Configuration options object.
* Creation options for {@link DatabaseManager}.
*
* @public
*/
@@ -46,15 +46,20 @@ export type DatabaseManagerOptions = {
migrations?: PluginDatabaseManager['migrations'];
};
/** @public */
/**
* Manages database connections for Backstage backend plugins.
*
* The database manager allows the user to set connection and client settings on
* a per pluginId basis by defining a database config block under
* `plugin.<pluginId>` in addition to top level defaults. Optionally, a user may
* set `prefix` which is used to prefix generated database names if config is
* not provided.
*
* @public
*/
export class DatabaseManager {
/**
* Creates a DatabaseManager from `backend.database` config.
*
* The database manager allows the user to set connection and client settings on a per pluginId
* basis by defining a database config block under `plugin.<pluginId>` in addition to top level
* defaults. Optionally, a user may set `prefix` which is used to prefix generated database
* names if config is not provided.
* Creates a {@link DatabaseManager} from `backend.database` config.
*
* @param config - The loaded application configuration.
* @param options - An optional configuration object.
@@ -108,7 +113,7 @@ export class DatabaseManager {
* which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is
* `schema`, it will fallback to using the default database for the knex instance.
*
* @param pluginId Lookup the database name for given plugin
* @param pluginId - Lookup the database name for given plugin
* @returns String representing the plugin's database name
*/
private getDatabaseName(pluginId: string): string | undefined {
@@ -143,12 +148,13 @@ export class DatabaseManager {
/**
* Provides the client type which should be used for a given plugin.
*
* The client type is determined by plugin specific config if present. Otherwise the base
* client is used as the fallback.
* The client type is determined by plugin specific config if present.
* Otherwise the base client is used as the fallback.
*
* @param pluginId Plugin to get the client type for
* @returns Object with client type returned as `client` and boolean representing whether
* or not the client was overridden as `overridden`
* @param pluginId - Plugin to get the client type for
* @returns Object with client type returned as `client` and boolean
* representing whether or not the client was overridden as
* `overridden`
*/
private getClientType(pluginId: string): {
client: string;
@@ -169,8 +175,8 @@ export class DatabaseManager {
/**
* Provides the knexConfig which should be used for a given plugin.
*
* @param pluginId Plugin to get the knexConfig for
* @returns the merged kexConfig value or undefined if it isn't specified
* @param pluginId - Plugin to get the knexConfig for
* @returns The merged knexConfig value or undefined if it isn't specified
*/
private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {
const pluginConfig = this.config
@@ -197,13 +203,15 @@ export class DatabaseManager {
}
/**
* Provides a Knex connection plugin config by combining base and plugin config.
* Provides a Knex connection plugin config by combining base and plugin
* config.
*
* This method provides a baseConfig for a plugin database connector. If the client type
* has not been overridden, the global connection config will be included with plugin
* specific config as the base. Values from the plugin connection take precedence over the
* base. Base database name is omitted for all supported databases excluding SQLite unless
* `pluginDivisionMode` is set to `schema`.
* This method provides a baseConfig for a plugin database connector. If the
* client type has not been overridden, the global connection config will be
* included with plugin specific config as the base. Values from the plugin
* connection take precedence over the base. Base database name is omitted for
* all supported databases excluding SQLite unless `pluginDivisionMode` is set
* to `schema`.
*/
private getConnectionConfig(
pluginId: string,
@@ -249,9 +257,10 @@ export class DatabaseManager {
/**
* Provides a Knex database config for a given plugin.
*
* This method provides a Knex configuration object along with the plugin's client type.
* This method provides a Knex configuration object along with the plugin's
* client type.
*
* @param pluginId The plugin that the database config should correspond with
* @param pluginId - The plugin that the database config should correspond with
*/
private getConfigForPlugin(pluginId: string): Knex.Config {
const { client } = this.getClientType(pluginId);
@@ -264,20 +273,21 @@ export class DatabaseManager {
}
/**
* Provides a partial Knex.Config database schema override for a given plugin.
* Provides a partial `Knex.Config` database schema override for a given
* plugin.
*
* @param pluginId Target plugin to get database schema override
* @returns Partial Knex.Config with database schema override
* @param pluginId - Target plugin to get database schema override
* @returns Partial `Knex.Config` with database schema override
*/
private getSchemaOverrides(pluginId: string): Knex.Config | undefined {
return createSchemaOverride(this.getClientType(pluginId).client, pluginId);
}
/**
* Provides a partial Knex.Config database name override for a given plugin.
* Provides a partial `Knex.Config` database name override for a given plugin.
*
* @param pluginId Target plugin to get database name override
* @returns Partial Knex.Config with database name override
* @param pluginId - Target plugin to get database name override
* @returns Partial `Knex.Config` with database name override
*/
private getDatabaseOverrides(pluginId: string): Knex.Config {
const databaseName = this.getDatabaseName(pluginId);
@@ -289,8 +299,9 @@ export class DatabaseManager {
/**
* Provides a scoped Knex client for a plugin as per application config.
*
* @param pluginId Plugin to get a Knex client for
* @returns Promise which resolves to a scoped Knex database client for a plugin
* @param pluginId - Plugin to get a Knex client for
* @returns Promise which resolves to a scoped Knex database client for a
* plugin
*/
private async getDatabase(pluginId: string): Promise<Knex> {
const pluginConfig = new ConfigReader(
@@ -58,7 +58,7 @@ export function createDatabaseClient(
}
/**
* Alias for createDatabaseClient
* Alias for {@link createDatabaseClient}
*
* @public
* @deprecated Use createDatabaseClient instead
@@ -100,7 +100,8 @@ export async function ensureSchemaExists(
}
/**
* Provides a Knex.Config object with the provided database name for a given client.
* Provides a `Knex.Config` object with the provided database name for a given
* client.
*/
export function createNameOverride(
client: string,
@@ -117,7 +118,8 @@ export function createNameOverride(
}
/**
* Provides a Knex.Config object with the provided database schema for a given client. Currently only supported by `pg`.
* Provides a `Knex.Config` object with the provided database schema for a given
* client. Currently only supported by `pg`.
*/
export function createSchemaOverride(
client: string,
@@ -156,7 +158,8 @@ export function parseConnectionString(
}
/**
* Normalizes a connection config or string into an object which can be passed to Knex.
* Normalizes a connection config or string into an object which can be passed
* to Knex.
*/
export function normalizeConnection(
connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,
@@ -38,7 +38,7 @@ export interface PluginDatabaseManager {
/**
* skip database migrations. Useful if connecting to a read-only database.
*
* @default false
* @defaultValue false
*/
skip?: boolean;
};
@@ -31,7 +31,11 @@ const coloredTemplate = (info: TransformableInfo) => {
return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;
};
/** @public */
/**
* A logging format that adds coloring to console output.
*
* @public
*/
export const coloredFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.colorize({
@@ -23,12 +23,29 @@ import { escapeRegExp } from '../util/escapeRegExp';
let rootLogger: winston.Logger;
let redactionRegExp: RegExp | undefined;
/** @public */
/**
* Gets the current root logger.
*
* @public
*/
export function getRootLogger(): winston.Logger {
return rootLogger;
}
/** @public */
/**
* Sets a completely custom default "root" logger.
*
* @remarks
*
* This is the logger instance that will be the foundation for all other logger
* instances passed to plugins etc, in a given backend.
*
* Only use this if you absolutely need to make a completely custom logger.
* Normally if you want to make light adaptations to the default logger
* behavior, you would instead call {@link createRootLogger}.
*
* @public
*/
export function setRootLogger(newLogger: winston.Logger) {
rootLogger = newLogger;
}
@@ -67,7 +84,17 @@ function redactLogLine(info: winston.Logform.TransformableInfo) {
return info;
}
/** @public */
/**
* Creates a default "root" logger. This also calls {@link setRootLogger} under
* the hood.
*
* @remarks
*
* This is the logger instance that will be the foundation for all other logger
* instances passed to plugins etc, in a given backend.
*
* @public
*/
export function createRootLogger(
options: winston.LoggerOptions = {},
env = process.env,
@@ -17,7 +17,7 @@
import {
AuthenticationError,
ConflictError,
ErrorResponse,
ErrorResponseBody,
InputError,
NotAllowedError,
NotFoundError,
@@ -28,7 +28,11 @@ import { ErrorRequestHandler, NextFunction, Request, Response } from 'express';
import { Logger } from 'winston';
import { getRootLogger } from '../logging';
/** @public */
/**
* Options passed to the {@link errorHandler} middleware.
*
* @public
*/
export type ErrorHandlerOptions = {
/**
* Whether error response bodies should show error stack traces or not.
@@ -89,7 +93,7 @@ export function errorHandler(
return;
}
const body: ErrorResponse = {
const body: ErrorResponseBody = {
error: serializeError(error, { includeStack: showStackTraces }),
request: { method: req.method, url: req.url },
response: { statusCode },
@@ -16,10 +16,19 @@
import { NextFunction, Request, Response, RequestHandler } from 'express';
/** @public */
/**
* A custom status checking function, passed to {@link statusCheckHandler} and
* {@link createStatusCheckRouter}.
*
* @public
*/
export type StatusCheck = () => Promise<any>;
/** @public */
/**
* Options passed to {@link statusCheckHandler}.
*
* @public
*/
export interface StatusCheckHandlerOptions {
/**
* Optional status function which returns a message.
@@ -94,6 +94,11 @@ const parseURL = (
};
};
/**
* Implements a {@link UrlReader} for AWS S3 buckets.
*
* @public
*/
export class AwsS3UrlReader implements UrlReader {
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
const integrations = ScmIntegrations.fromConfig(config);
@@ -38,7 +38,11 @@ import {
ReadUrlResponse,
} from './types';
/** @public */
/**
* Implements a {@link UrlReader} for Azure repos.
*
* @public
*/
export class AzureUrlReader implements UrlReader {
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
const integrations = ScmIntegrations.fromConfig(config);
@@ -41,8 +41,8 @@ import {
} from './types';
/**
* A processor that adds the ability to read files from Bitbucket v1 and v2 APIs, such as
* the one exposed by Bitbucket Cloud itself.
* Implements a {@link UrlReader} for files from Bitbucket v1 and v2 APIs, such
* as the one exposed by Bitbucket Cloud itself.
*
* @public
*/
@@ -27,7 +27,7 @@ import {
import path from 'path';
/**
* A UrlReader that does a plain fetch of the URL.
* A {@link UrlReader} that does a plain fetch of the URL.
*
* @public
*/
@@ -50,7 +50,7 @@ export type GhBlobResponse =
RestEndpointMethodTypes['git']['getBlob']['response']['data'];
/**
* A processor that adds the ability to read files from GitHub v3 APIs, such as
* Implements a {@link UrlReader} for files through the GitHub v3 APIs, such as
* the one exposed by GitHub itself.
*
* @public
@@ -39,7 +39,11 @@ import {
} from './types';
import { trimEnd } from 'lodash';
/** @public */
/**
* Implements a {@link UrlReader} for files on GitLab.
*
* @public
*/
export class GitlabUrlReader implements UrlReader {
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
const integrations = ScmIntegrations.fromConfig(config);
@@ -48,7 +48,11 @@ const parseURL = (
};
};
/** @public */
/**
* Implements a {@link UrlReader} for files on Google GCS.
*
* @public
*/
export class GoogleGcsUrlReader implements UrlReader {
static factory: ReaderFactory = ({ config, logger }) => {
if (!config.has('integrations.googleGcs')) {
@@ -27,7 +27,11 @@ import { FetchUrlReader } from './FetchUrlReader';
import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
import { AwsS3UrlReader } from './AwsS3UrlReader';
/** @public */
/**
* Creation options for {@link UrlReaders}.
*
* @public
*/
export type UrlReadersOptions = {
/** Root config object */
config: Config;
@@ -38,13 +42,13 @@ export type UrlReadersOptions = {
};
/**
* UrlReaders provide various utilities related to the UrlReader interface.
* Helps construct {@link UrlReader}s.
*
* @public
*/
export class UrlReaders {
/**
* Creates a UrlReader without any known types.
* Creates a custom {@link UrlReader} wrapper for your own set of factories.
*/
static create(options: UrlReadersOptions): UrlReader {
const { logger, config, factories } = options;
@@ -65,7 +69,8 @@ export class UrlReaders {
}
/**
* Creates a UrlReader that includes all the default factories from this package.
* Creates a {@link UrlReader} wrapper that includes all the default factories
* from this package.
*
* Any additional factories passed will be loaded before the default ones.
*/
+6 -1
View File
@@ -281,7 +281,12 @@ export type FromReadableArrayOptions = Array<{
path: string;
}>;
/** @public */
/**
* A factory for response factories that handle the unpacking and inspection of
* complex responses such as archive data.
*
* @public
*/
export interface ReadTreeResponseFactory {
fromTarArchive(
options: ReadTreeResponseFactoryOptions,
+5 -1
View File
@@ -33,7 +33,11 @@ From : https://isomorphic-git.org/docs/en/onAuth with fix for GitHub
Azure 'notempty' token
*/
/** @public */
/**
* A convenience wrapper around the `isomorphic-git` library.
*
* @public
*/
export class Git {
private constructor(
private readonly config: {
@@ -19,9 +19,26 @@ import Router from 'express-promise-router';
import express from 'express';
import { errorHandler, statusCheckHandler, StatusCheck } from '../middleware';
/** @public */
/**
* Creates a default status checking router, that you can add to your express
* app.
*
* @remarks
*
* This adds a `/healthcheck` route (or any other path, if given as an
* argument), which your infra can call to see if the service is ready to serve
* requests.
*
* @public
*/
export async function createStatusCheckRouter(options: {
logger: Logger;
/**
* The path (including a leading slash) that the health check should be
* mounted on.
*
* @defaultValue '/healthcheck'
*/
path?: string;
/**
* If not implemented, the default express middleware always returns 200.
+10 -2
View File
@@ -20,7 +20,11 @@ import { Router, RequestHandler, ErrorRequestHandler } from 'express';
import { Server } from 'http';
import { Logger } from 'winston';
/** @public */
/**
* A helper for building backend service instances.
*
* @public
*/
export type ServiceBuilder = {
/**
* Sets the service parameters based on configuration.
@@ -119,5 +123,9 @@ export type ServiceBuilder = {
start(): Promise<Server>;
};
/** @public */
/**
* A factory for request loggers.
*
* @public
*/
export type RequestLoggingHandlerFactory = (logger?: Logger) => RequestHandler;
@@ -16,7 +16,11 @@
import { Writable } from 'stream';
/** @public */
/**
* Options passed to the {@link ContainerRunner.runContainer} method.
*
* @public
*/
export type RunContainerOptions = {
imageName: string;
command?: string | string[];
@@ -28,7 +32,14 @@ export type RunContainerOptions = {
pullImage?: boolean;
};
/** @public */
/**
* Handles the running of containers, on behalf of others.
*
* @public
*/
export interface ContainerRunner {
/**
* Runs a container image to completion.
*/
runContainer(opts: RunContainerOptions): Promise<void>;
}
@@ -24,7 +24,11 @@ export type UserOptions = {
User?: string;
};
/** @public */
/**
* A {@link ContainerRunner} for Docker containers.
*
* @public
*/
export class DockerContainerRunner implements ContainerRunner {
private readonly dockerClient: Docker;
+4
View File
@@ -80,6 +80,10 @@ module.exports = {
name: '@material-ui/icons',
message: "Please import '@material-ui/icons/<Icon>' instead.",
},
{
name: '@material-ui/icons/', // because this is possible too ._.
message: "Please import '@material-ui/icons/<Icon>' instead.",
},
...require('module').builtinModules,
],
// Avoid cross-package imports
+10 -9
View File
@@ -51,8 +51,8 @@
"@svgr/rollup": "5.5.x",
"@svgr/webpack": "5.5.x",
"@types/webpack-env": "^1.15.2",
"@typescript-eslint/eslint-plugin": "^v4.33.0",
"@typescript-eslint/parser": "^v4.28.3",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"@yarnpkg/lockfile": "^1.1.0",
"bfj": "^7.0.2",
"buffer": "^6.0.3",
@@ -61,16 +61,17 @@
"commander": "^6.1.0",
"css-loader": "^6.5.1",
"diff": "^5.0.0",
"esbuild": "^0.14.1",
"eslint": "^7.30.0",
"esbuild": "^0.14.10",
"esbuild-loader": "^2.18.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-formatter-friendly": "^7.0.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^25.3.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-monorepo": "^0.3.2",
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-react-hooks": "^4.0.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"express": "^4.17.1",
"fork-ts-checker-webpack-plugin": "^4.0.5",
"fs-extra": "9.1.0",
+8 -13
View File
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import { WebpackOptionsNormalized, WebpackPluginInstance } from 'webpack';
import TerserPlugin from 'terser-webpack-plugin';
import { WebpackOptionsNormalized } from 'webpack';
import { BundlingOptions } from './types';
import { isParallelDefault } from '../parallel';
const { ESBuildMinifyPlugin } = require('esbuild-loader');
export const optimization = (
options: BundlingOptions,
@@ -26,16 +26,11 @@ export const optimization = (
return {
minimize: !isDev,
// Only configure when parallel is explicitly overridden from the default
...(!isParallelDefault(options.parallel)
? {
minimizer: [
new TerserPlugin({
parallel: options.parallel,
}) as unknown as WebpackPluginInstance,
],
}
: {}),
minimizer: [
new ESBuildMinifyPlugin({
target: 'es2019',
}),
],
runtimeChunk: 'single',
splitChunks: {
automaticNameDelimiter: '-',
+55 -8
View File
@@ -265,6 +265,19 @@ describe('ConfigReader', () => {
withLogCollector(() => config.getOptionalConfigArray('b')),
).toMatchObject({ warn: [] });
});
it('should coerce number strings to numbers', () => {
const config = ConfigReader.fromConfigs([
{
data: {
port: '123',
},
context: '1',
},
]);
expect(config.getNumber('port')).toEqual(123);
});
});
describe('ConfigReader with fallback', () => {
@@ -661,16 +674,50 @@ describe('ConfigReader.get()', () => {
});
});
it('coerces number strings to numbers', () => {
const config = ConfigReader.fromConfigs([
{
data: {
port: '123',
},
context: '1',
it('should return deep clones of the backing data', () => {
const data1 = {
foo: {
bar: [],
baz: {},
},
};
const data2 = {
x: {
y: {
z: {},
},
},
};
const reader = ConfigReader.fromConfigs([
{ data: data1, context: '1' },
{ data: data2, context: '2' },
]);
expect(config.getNumber('port')).toEqual(123);
reader.get<any>().foo.bar.push(1);
reader.get<any>('foo').bar.push(1);
reader.get<any>('foo.bar').push(1);
reader.get<any>().foo.baz.x = 1;
reader.get<any>('foo').baz.x = 1;
reader.get<any>('foo.baz').x = 1;
reader.get<any>().x.y.z.w = 1;
reader.get<any>('x').y.z.w = 1;
reader.get<any>('x.y').z.w = 1;
reader.get<any>('x.y.z').w = 1;
const readerSingle = ConfigReader.fromConfigs([
{ data: data1, context: '1' },
]);
readerSingle.get<any>().foo.bar.push(1);
readerSingle.get<any>('foo').bar.push(1);
readerSingle.get<any>('foo.bar').push(1);
readerSingle.get<any>().foo.baz.x = 1;
readerSingle.get<any>('foo').baz.x = 1;
readerSingle.get<any>('foo.baz').x = 1;
expect(data1.foo.bar).toEqual([]);
expect(data1.foo.baz).toEqual({});
expect(data2.x.y.z).toEqual({});
});
});
+3 -6
View File
@@ -126,7 +126,7 @@ export class ConfigReader implements Config {
/** {@inheritdoc Config.getOptional} */
getOptional<T = JsonValue>(key?: string): T | undefined {
const value = this.readValue(key);
const value = cloneDeep(this.readValue(key));
const fallbackValue = this.fallback?.getOptional<T>(key);
if (value === undefined) {
@@ -153,11 +153,8 @@ export class ConfigReader implements Config {
// Avoid merging arrays and primitive values, since that's how merging works for other
// methods for reading config.
return mergeWith(
{},
{ value: cloneDeep(fallbackValue) },
{ value },
(into, from) => (!isObject(from) || !isObject(into) ? from : undefined),
return mergeWith({}, { value: fallbackValue }, { value }, (into, from) =>
!isObject(from) || !isObject(into) ? from : undefined,
).value as T;
}
+13 -24
View File
@@ -50,7 +50,6 @@ import { oktaAuthApiRef } from '@backstage/core-plugin-api';
import { oneloginAuthApiRef } from '@backstage/core-plugin-api';
import { OpenIdConnectApi } from '@backstage/core-plugin-api';
import { PendingOAuthRequest } from '@backstage/core-plugin-api';
import { PluginOutput } from '@backstage/core-plugin-api';
import { ProfileInfo } from '@backstage/core-plugin-api';
import { ProfileInfoApi } from '@backstage/core-plugin-api';
import { PropsWithChildren } from 'react';
@@ -129,21 +128,6 @@ export type ApiProviderProps = {
children: ReactNode;
};
// @public @deprecated
export class ApiRegistry implements ApiHolder {
constructor(apis: Map<string, unknown>);
// Warning: (ae-forgotten-export) The symbol "ApiRegistryBuilder" needs to be exported by the entry point index.d.ts
//
// (undocumented)
static builder(): ApiRegistryBuilder;
// Warning: (ae-forgotten-export) The symbol "ApiImpl" needs to be exported by the entry point index.d.ts
static from(apis: ApiImpl[]): ApiRegistry;
// (undocumented)
get<T>(api: ApiRef<T>): T | undefined;
static with<T>(api: ApiRef<T>, impl: T): ApiRegistry;
with<T>(api: ApiRef<T>, impl: T): ApiRegistry;
}
// @public
export class ApiResolver implements ApiHolder {
constructor(factories: ApiFactoryHolder);
@@ -208,14 +192,19 @@ export type AppOptions = {
icons: AppIcons & {
[key in string]: IconComponent;
};
plugins?: (Omit<BackstagePlugin<any, any>, 'output'> & {
output(): (
| PluginOutput
| {
type: string;
}
)[];
})[];
plugins?: Array<
BackstagePlugin<any, any> & {
output?(): Array<
| {
type: 'feature-flag';
name: string;
}
| {
type: string;
}
>;
}
>;
components: AppComponents;
themes: (Partial<AppTheme> & Omit<AppTheme, 'theme'>)[];
configLoader?: AppConfigLoader;
@@ -18,6 +18,7 @@ import { ApiRef, ApiHolder } from '@backstage/core-plugin-api';
type ApiImpl<T = unknown> = readonly [ApiRef<T>, T];
/** @internal */
class ApiRegistryBuilder {
private apis: [string, unknown][] = [];
@@ -35,8 +36,7 @@ class ApiRegistryBuilder {
/**
* A registry for utility APIs.
*
* @public
* @deprecated Will be removed, use {@link @backstage/test-utils#TestApiProvider} or {@link @backstage/test-utils#TestApiRegistry} instead.
* @internal
*/
export class ApiRegistry implements ApiHolder {
static builder() {
@@ -16,7 +16,6 @@
export { ApiProvider } from './ApiProvider';
export type { ApiProviderProps } from './ApiProvider';
export { ApiRegistry } from './ApiRegistry';
export { ApiResolver } from './ApiResolver';
export { ApiFactoryRegistry } from './ApiFactoryRegistry';
export type { ApiFactoryScope } from './ApiFactoryRegistry';
@@ -316,7 +316,7 @@ describe('Integration Test', () => {
plugins: [
createPlugin({
id: 'test',
register: p => p.featureFlags.register('name'),
featureFlags: [{ name: 'name' }],
}),
],
components,
@@ -384,8 +384,24 @@ describe('Integration Test', () => {
name: 'foo',
},
],
register: p => p.featureFlags.register('name'),
}),
// We still support consuming the old feature flag API for a little while longer
{
getId() {
return 'old-test';
},
getApis() {
return [];
},
output() {
return [
{
type: 'feature-flag',
name: 'old-feature-flag',
},
];
},
} as any,
],
components,
configLoader: async () => [],
@@ -412,12 +428,12 @@ describe('Integration Test', () => {
);
expect(storageFlags.registerFlag).toHaveBeenCalledWith({
name: 'name',
name: 'foo',
pluginId: 'test',
});
expect(storageFlags.registerFlag).toHaveBeenCalledWith({
name: 'foo',
pluginId: 'test',
name: 'old-feature-flag',
pluginId: 'old-test',
});
});
+17 -18
View File
@@ -27,7 +27,6 @@ import { Route, Routes } from 'react-router-dom';
import useAsync from 'react-use/lib/useAsync';
import {
ApiProvider,
ApiRegistry,
AppThemeSelector,
ConfigReader,
LocalStorageFeatureFlags,
@@ -80,6 +79,13 @@ import {
} from './types';
import { AppThemeProvider } from './AppThemeProvider';
import { defaultConfigLoader } from './defaultConfigLoader';
import { ApiRegistry } from '../apis/system/ApiRegistry';
type CompatiblePlugin =
| BackstagePlugin<any, any>
| (Omit<BackstagePlugin<any, any>, 'getFeatureFlags'> & {
output(): Array<{ type: 'feature-flag'; name: string }>;
});
export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) {
const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();
@@ -149,7 +155,7 @@ function useConfigLoader(
if (noConfigNode) {
return {
node: (
<ApiProvider apis={ApiRegistry.from([[appThemeApiRef, appThemeApi]])}>
<ApiProvider apis={ApiRegistry.with(appThemeApiRef, appThemeApi)}>
<ThemeProvider>{noConfigNode}</ThemeProvider>
</ApiProvider>
),
@@ -183,7 +189,7 @@ export class AppManager implements BackstageApp {
private readonly apis: Iterable<AnyApiFactory>;
private readonly icons: NonNullable<AppOptions['icons']>;
private readonly plugins: Set<BackstagePlugin<any, any>>;
private readonly plugins: Set<CompatiblePlugin>;
private readonly components: AppComponents;
private readonly themes: AppTheme[];
private readonly configLoader?: AppConfigLoader;
@@ -196,9 +202,7 @@ export class AppManager implements BackstageApp {
constructor(options: AppOptions) {
this.apis = options.apis ?? [];
this.icons = options.icons;
this.plugins = new Set(
(options.plugins as BackstagePlugin<any, any>[]) ?? [],
);
this.plugins = new Set((options.plugins as CompatiblePlugin[]) ?? []);
this.components = options.components;
this.themes = options.themes as AppTheme[];
this.configLoader = options.configLoader ?? defaultConfigLoader;
@@ -208,7 +212,7 @@ export class AppManager implements BackstageApp {
}
getPlugins(): BackstagePlugin<any, any>[] {
return Array.from(this.plugins);
return Array.from(this.plugins) as BackstagePlugin<any, any>[];
}
getSystemIcon(key: string): IconComponent | undefined {
@@ -282,16 +286,11 @@ export class AppManager implements BackstageApp {
}
} else {
for (const output of plugin.output()) {
switch (output.type) {
case 'feature-flag': {
featureFlagsApi.registerFlag({
name: output.name,
pluginId: plugin.getId(),
});
break;
}
default:
break;
if (output.type === 'feature-flag') {
featureFlagsApi.registerFlag({
name: output.name,
pluginId: plugin.getId(),
});
}
}
}
@@ -468,7 +467,7 @@ export class AppManager implements BackstageApp {
return this.apiHolder;
}
private verifyPlugins(plugins: Iterable<BackstagePlugin>) {
private verifyPlugins(plugins: Iterable<CompatiblePlugin>) {
const pluginIds = new Set<string>();
for (const plugin of plugins) {
+7 -4
View File
@@ -24,7 +24,6 @@ import {
RouteRef,
SubRouteRef,
ExternalRouteRef,
PluginOutput,
IdentityApi,
} from '@backstage/core-plugin-api';
import { AppConfig } from '@backstage/config';
@@ -232,9 +231,13 @@ export type AppOptions = {
/**
* A list of all plugins to include in the app.
*/
plugins?: (Omit<BackstagePlugin<any, any>, 'output'> & {
output(): (PluginOutput | { type: string })[];
})[];
plugins?: Array<
BackstagePlugin<any, any> & {
output?(): Array<
{ type: 'feature-flag'; name: string } | { type: string }
>; // support for old plugins
}
>;
/**
* Supply components to the app to override the default ones.
@@ -61,14 +61,14 @@ export function ResponseErrorPanel(props: ErrorPanelProps) {
);
}
const { data, cause } = error as ResponseError;
const { request, response } = data;
const { body, cause } = error as ResponseError;
const { request, response } = body;
const errorString = `${response.statusCode}: ${cause.name}`;
const requestString = request && `${request.method} ${request.url}`;
const messageString = cause.message.replace(/\\n/g, '\n');
const stackString = cause.stack?.replace(/\\n/g, '\n');
const jsonString = JSON.stringify(data, undefined, 2);
const jsonString = JSON.stringify(body, undefined, 2);
return (
<ErrorPanel
-16
View File
@@ -241,7 +241,6 @@ export type BackstagePlugin<
ExternalRoutes extends AnyExternalRoutes = {},
> = {
getId(): string;
output(): PluginOutput[];
getApis(): Iterable<AnyApiFactory>;
getFeatureFlags(): Iterable<PluginFeatureFlagConfig>;
provide<T>(extension: Extension<T>): T;
@@ -446,12 +445,6 @@ export type FeatureFlag = {
pluginId: string;
};
// @public @deprecated
export type FeatureFlagOutput = {
type: 'feature-flag';
name: string;
};
// @public
export interface FeatureFlagsApi {
getRegisteredFlags(): FeatureFlag[];
@@ -687,7 +680,6 @@ export type PluginConfig<
> = {
id: string;
apis?: Iterable<AnyApiFactory>;
register?(hooks: PluginHooks): void;
routes?: Routes;
externalRoutes?: ExternalRoutes;
featureFlags?: PluginFeatureFlagConfig[];
@@ -698,14 +690,6 @@ export type PluginFeatureFlagConfig = {
name: string;
};
// @public @deprecated
export type PluginHooks = {
featureFlags: FeatureFlagsHooks;
};
// @public @deprecated
export type PluginOutput = FeatureFlagOutput;
// @public
export type ProfileInfo = {
email?: string;
@@ -28,62 +28,7 @@ describe('Plugin Feature Flag', () => {
expect(
createPlugin({
id: 'test',
register({ featureFlags }) {
featureFlags.register('blob');
},
}).getFeatureFlags(),
).toEqual([{ name: 'blob' }]);
expect(
createPlugin({
id: 'test',
register({ featureFlags }) {
featureFlags.register('blob');
},
featureFlags: [{ name: 'test' }],
}).getFeatureFlags(),
).toEqual([{ name: 'test' }, { name: 'blob' }]);
expect(
createPlugin({
id: 'test',
}).getFeatureFlags(),
).toEqual([]);
/* deprecated tests */
expect(
createPlugin({
id: 'test',
featureFlags: [{ name: 'test' }],
}).output(),
).toEqual([{ name: 'test', type: 'feature-flag' }]);
expect(
createPlugin({
id: 'test',
register({ featureFlags }) {
featureFlags.register('blob');
},
}).output(),
).toEqual([{ name: 'blob', type: 'feature-flag' }]);
expect(
createPlugin({
id: 'test',
register({ featureFlags }) {
featureFlags.register('blob');
},
featureFlags: [{ name: 'test' }],
}).output(),
).toEqual([
{ name: 'test', type: 'feature-flag' },
{ name: 'blob', type: 'feature-flag' },
]);
expect(
createPlugin({
id: 'test',
}).output(),
).toEqual([]);
});
});
+1 -36
View File
@@ -16,7 +16,6 @@
import {
PluginConfig,
PluginOutput,
BackstagePlugin,
Extension,
AnyRoutes,
@@ -33,8 +32,6 @@ export class PluginImpl<
ExternalRoutes extends AnyExternalRoutes,
> implements BackstagePlugin<Routes, ExternalRoutes>
{
private storedOutput?: PluginOutput[];
constructor(private readonly config: PluginConfig<Routes, ExternalRoutes>) {}
getId(): string {
@@ -46,11 +43,7 @@ export class PluginImpl<
}
getFeatureFlags(): Iterable<PluginFeatureFlagConfig> {
const registeredFlags = this.output()
.filter(({ type }) => type === 'feature-flag')
.map(({ name }) => ({ name }));
return registeredFlags;
return this.config.featureFlags?.slice() ?? [];
}
get routes(): Routes {
@@ -61,34 +54,6 @@ export class PluginImpl<
return this.config.externalRoutes ?? ({} as ExternalRoutes);
}
output(): PluginOutput[] {
if (this.storedOutput) {
return this.storedOutput;
}
const outputs = new Array<PluginOutput>();
this.storedOutput = outputs;
if (this.config.featureFlags) {
for (const flag of this.config.featureFlags) {
outputs.push({ type: 'feature-flag', name: flag.name });
}
}
if (!this.config.register) {
return outputs;
}
this.config.register({
featureFlags: {
register(name) {
outputs.push({ type: 'feature-flag', name });
},
},
});
return this.storedOutput;
}
provide<T>(extension: Extension<T>): T {
return extension.expose(this);
}
@@ -20,10 +20,7 @@ export type {
AnyRoutes,
BackstagePlugin,
Extension,
FeatureFlagOutput,
FeatureFlagsHooks,
PluginConfig,
PluginHooks,
PluginOutput,
PluginFeatureFlagConfig,
} from './types';
@@ -17,24 +17,6 @@
import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing';
import { AnyApiFactory } from '../apis/system';
/**
* Replace with using {@link RouteRef}s.
* @deprecated will be removed
* @public
*/
export type FeatureFlagOutput = {
type: 'feature-flag';
name: string;
};
/**
* {@link FeatureFlagOutput} type.
*
* @public
* @deprecated Use {@link BackstagePlugin.getFeatureFlags} instead.
*/
export type PluginOutput = FeatureFlagOutput;
/**
* Plugin extension type.
*
@@ -72,10 +54,6 @@ export type BackstagePlugin<
ExternalRoutes extends AnyExternalRoutes = {},
> = {
getId(): string;
/**
* @deprecated use getFeatureFlags instead.
* */
output(): PluginOutput[];
getApis(): Iterable<AnyApiFactory>;
/**
* Returns all registered feature flags for this plugin.
@@ -107,23 +85,11 @@ export type PluginConfig<
> = {
id: string;
apis?: Iterable<AnyApiFactory>;
/** @deprecated use featureFlags property instead for defining feature flags */
register?(hooks: PluginHooks): void;
routes?: Routes;
externalRoutes?: ExternalRoutes;
featureFlags?: PluginFeatureFlagConfig[];
};
/**
* Holds hooks registered by the plugin.
*
* @deprecated - feature flags are now registered in plugin config under featureFlags
* @public
*/
export type PluginHooks = {
featureFlags: FeatureFlagsHooks;
};
/**
* Interface for registering feature flags hooks.
*
+2
View File
@@ -67,6 +67,8 @@
"@backstage/plugin-explore": "*",
"@backstage/plugin-github-actions": "*",
"@backstage/plugin-lighthouse": "*",
"@backstage/plugin-permission-common": "*",
"@backstage/plugin-permission-node": "*",
"@backstage/plugin-proxy-backend": "*",
"@backstage/plugin-rollbar-backend": "*",
"@backstage/plugin-scaffolder": "*",
+4
View File
@@ -57,6 +57,8 @@ import { version as pluginExplore } from '../../../../plugins/explore/package.js
import { version as pluginGithubActions } from '../../../../plugins/github-actions/package.json';
import { version as pluginLighthouse } from '../../../../plugins/lighthouse/package.json';
import { version as pluginOrg } from '../../../../plugins/org/package.json';
import { version as pluginPermissionCommon } from '../../../../plugins/permission-common/package.json';
import { version as pluginPermissionNode } from '../../../../plugins/permission-node/package.json';
import { version as pluginProxyBackend } from '../../../../plugins/proxy-backend/package.json';
import { version as pluginRollbarBackend } from '../../../../plugins/rollbar-backend/package.json';
import { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json';
@@ -94,6 +96,8 @@ export const packageVersions = {
'@backstage/plugin-github-actions': pluginGithubActions,
'@backstage/plugin-lighthouse': pluginLighthouse,
'@backstage/plugin-org': pluginOrg,
'@backstage/plugin-permission-common': pluginPermissionCommon,
'@backstage/plugin-permission-node': pluginPermissionNode,
'@backstage/plugin-proxy-backend': pluginProxyBackend,
'@backstage/plugin-rollbar-backend': pluginRollbarBackend,
'@backstage/plugin-scaffolder': pluginScaffolder,
@@ -23,6 +23,8 @@
"@backstage/plugin-app-backend": "^{{version '@backstage/plugin-app-backend'}}",
"@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}",
"@backstage/plugin-catalog-backend": "^{{version '@backstage/plugin-catalog-backend'}}",
"@backstage/plugin-permission-common": "^{{version '@backstage/plugin-permission-common'}}",
"@backstage/plugin-permission-node": "^{{version '@backstage/plugin-permission-node'}}",
"@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}",
"@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}",
"@backstage/plugin-search-backend": "^{{version '@backstage/plugin-search-backend'}}",
@@ -29,6 +29,7 @@ import proxy from './plugins/proxy';
import techdocs from './plugins/techdocs';
import search from './plugins/search';
import { PluginEnvironment } from './types';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
function makeCreateEnv(config: Config) {
const root = getRootLogger();
@@ -38,6 +39,10 @@ function makeCreateEnv(config: Config) {
const databaseManager = DatabaseManager.fromConfig(config);
const tokenManager = ServerTokenManager.noop();
const taskScheduler = TaskScheduler.fromConfig(config);
const permissions = ServerPermissionClient.fromConfig(config, {
discovery,
tokenManager,
});
root.info(`Created UrlReader ${reader}`);
@@ -55,6 +60,7 @@ function makeCreateEnv(config: Config) {
discovery,
tokenManager,
scheduler,
permissions,
};
};
}
@@ -8,6 +8,7 @@ import {
UrlReader,
} from '@backstage/backend-common';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
export type PluginEnvironment = {
logger: Logger;
@@ -18,4 +19,5 @@ export type PluginEnvironment = {
discovery: PluginEndpointDiscovery;
tokenManager: TokenManager;
scheduler: PluginTaskScheduler;
permissions: PermissionAuthorizer;
};
-15
View File
@@ -33,9 +33,6 @@ export type ErrorLike = {
[unknownKeys: string]: unknown;
};
// @public @deprecated
export type ErrorResponse = ErrorResponseBody;
// @public
export type ErrorResponseBody = {
error: SerializedError;
@@ -68,9 +65,6 @@ export class NotFoundError extends CustomErrorBase {}
// @public
export class NotModifiedError extends CustomErrorBase {}
// @public @deprecated
export function parseErrorResponse(response: Response): Promise<ErrorResponse>;
// @public
export function parseErrorResponseBody(
response: Response,
@@ -78,17 +72,8 @@ export function parseErrorResponseBody(
// @public
export class ResponseError extends Error {
// @deprecated
constructor(props: {
message: string;
response: Response;
data: ErrorResponseBody;
cause: Error;
});
readonly body: ErrorResponseBody;
readonly cause: Error;
// @deprecated
get data(): ErrorResponseBody;
static fromResponse(response: Response): Promise<ResponseError>;
readonly response: Response;
}
@@ -14,12 +14,12 @@
* limitations under the License.
*/
import { ErrorResponse } from '../serialization';
import { ErrorResponseBody } from '../serialization';
import { ResponseError } from './ResponseError';
describe('ResponseError', () => {
it('constructs itself from a response', async () => {
const body: ErrorResponse = {
const body: ErrorResponseBody = {
error: { name: 'Fours', message: 'Expected fives', stack: 'lines' },
request: { method: 'GET', url: '/' },
response: { statusCode: 444 },
+1 -13
View File
@@ -75,10 +75,7 @@ export class ResponseError extends Error {
});
}
/**
* @deprecated will be removed.
**/
constructor(props: {
private constructor(props: {
message: string;
response: Response;
data: ErrorResponseBody;
@@ -90,13 +87,4 @@ export class ResponseError extends Error {
this.body = props.data;
this.cause = props.cause;
}
/**
* The parsed JSON error body, as sent by the server.
* @deprecated use body instead.
*/
get data(): ErrorResponseBody {
// eslint-disable-next-line no-console
console.warn('ErrorResponse.data is deprecated, use .body instead.');
return this.body;
}
}
+2 -2
View File
@@ -16,5 +16,5 @@
export { deserializeError, serializeError, stringifyError } from './error';
export type { SerializedError } from './error';
export { parseErrorResponse, parseErrorResponseBody } from './response';
export type { ErrorResponse, ErrorResponseBody } from './response';
export { parseErrorResponseBody } from './response';
export type { ErrorResponseBody } from './response';
@@ -16,14 +16,6 @@
import { SerializedError } from './error';
/**
* A standard shape of JSON data returned as the body of backend errors.
*
* @public
* @deprecated - Use {@link ErrorResponseBody} instead.
*/
export type ErrorResponse = ErrorResponseBody;
/**
* A standard shape of JSON data returned as the body of backend errors.
*
@@ -48,25 +40,6 @@ export type ErrorResponseBody = {
};
};
/**
* Attempts to construct an ErrorResponse out of a failed server request.
* Assumes that the response has already been checked to be not ok. This
* function consumes the body of the response, and assumes that it hasn't
* been consumed before.
*
* The code is forgiving, and constructs a useful synthetic body as best it can
* if the response body wasn't on the expected form.
*
* @public
* @param response - The response of a failed request
* @deprecated - Use {@link parseErrorResponseBody} instead.
*/
export async function parseErrorResponse(
response: Response,
): Promise<ErrorResponse> {
return parseErrorResponseBody(response);
}
/**
* Attempts to construct an ErrorResponseBody out of a failed server request.
* Assumes that the response has already been checked to be not ok. This
+1
View File
@@ -8,6 +8,7 @@ const BACKSTAGE_CORE_STORIES = [
'packages/core-components',
'plugins/org',
'plugins/search',
'plugins/home',
];
module.exports = ({ args }) => {
+3
View File
@@ -9,6 +9,9 @@
},
"dependencies": {
"@backstage/theme": "^0.2.0",
"@backstage/test-utils": "^0.2.1",
"@backstage/core-app-api": "^0.3.1",
"@backstage/core-plugin-api": "^0.4.1",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
+40
View File
@@ -8,10 +8,13 @@ import { AnalyticsEvent } from '@backstage/core-plugin-api';
import { ApiHolder } from '@backstage/core-plugin-api';
import { ApiRef } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import { Config } from '@backstage/config';
import { ConfigApi } from '@backstage/core-plugin-api';
import { ErrorApi } from '@backstage/core-plugin-api';
import { ErrorApiError } from '@backstage/core-plugin-api';
import { ErrorApiErrorContext } from '@backstage/core-plugin-api';
import { ExternalRouteRef } from '@backstage/core-plugin-api';
import { JsonObject } from '@backstage/types';
import { JsonValue } from '@backstage/types';
import { Observable } from '@backstage/types';
import { ReactElement } from 'react';
@@ -52,6 +55,43 @@ export class MockAnalyticsApi implements AnalyticsApi {
// @public
export function mockBreakpoint(options: { matches: boolean }): void;
// @public
export class MockConfigApi implements ConfigApi {
constructor(data: JsonObject);
// (undocumented)
get<T = JsonValue>(key?: string): T;
// (undocumented)
getBoolean(key: string): boolean;
// (undocumented)
getConfig(key: string): Config;
// (undocumented)
getConfigArray(key: string): Config[];
// (undocumented)
getNumber(key: string): number;
// (undocumented)
getOptional<T = JsonValue>(key?: string): T | undefined;
// (undocumented)
getOptionalBoolean(key: string): boolean | undefined;
// (undocumented)
getOptionalConfig(key: string): Config | undefined;
// (undocumented)
getOptionalConfigArray(key: string): Config[] | undefined;
// (undocumented)
getOptionalNumber(key: string): number | undefined;
// (undocumented)
getOptionalString(key: string): string | undefined;
// (undocumented)
getOptionalStringArray(key: string): string[] | undefined;
// (undocumented)
getString(key: string): string;
// (undocumented)
getStringArray(key: string): string[];
// (undocumented)
has(key: string): boolean;
// (undocumented)
keys(): string[];
}
// @public
export class MockErrorApi implements ErrorApi {
constructor(options?: MockErrorApiOptions);
+1
View File
@@ -29,6 +29,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/config": "^0.1.11",
"@backstage/core-app-api": "^0.3.0",
"@backstage/core-plugin-api": "^0.4.0",
"@backstage/theme": "^0.2.14",
@@ -0,0 +1,42 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MockConfigApi } from './MockConfigApi';
describe('MockConfigApi', () => {
it('is able to read some basic config', () => {
const mock = new MockConfigApi({
app: {
title: 'Hello',
},
x: 1,
y: false,
z: [{ a: 3 }],
});
expect(mock.getString('app.title')).toEqual('Hello');
expect(mock.getNumber('x')).toEqual(1);
expect(mock.getBoolean('y')).toEqual(false);
expect(mock.getConfigArray('z')[0].getOptionalNumber('a')).toEqual(3);
expect(() => mock.getString('x')).toThrow(
"Invalid type in config for key 'x' in 'mock-config', got number, wanted string",
);
expect(() => mock.getString('missing')).toThrow(
"Missing required config value at 'missing'",
);
});
});
@@ -0,0 +1,111 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Config, ConfigReader } from '@backstage/config';
import { JsonObject, JsonValue } from '@backstage/types';
import { ConfigApi } from '@backstage/core-plugin-api';
/**
* MockConfigApi is a thin wrapper around {@link @backstage/config#ConfigReader}
* that can be used to mock configuration using a plain object.
*
* @public
* @example
* ```tsx
* const mockConfig = new MockConfigApi({
* app: { baseUrl: 'https://example.com' },
* });
*
* const rendered = await renderInTestApp(
* <TestApiProvider apis={[[configApiRef, mockConfig]]}>
* <MyTestedComponent />
* </TestApiProvider>,
* );
* ```
*/
export class MockConfigApi implements ConfigApi {
private readonly config: ConfigReader;
// NOTE: not extending in order to avoid inheriting the static `.fromConfigs`
constructor(data: JsonObject) {
this.config = new ConfigReader(data);
}
/** {@inheritdoc @backstage/config#Config.has} */
has(key: string): boolean {
return this.config.has(key);
}
/** {@inheritdoc @backstage/config#Config.keys} */
keys(): string[] {
return this.config.keys();
}
/** {@inheritdoc @backstage/config#Config.get} */
get<T = JsonValue>(key?: string): T {
return this.config.get(key);
}
/** {@inheritdoc @backstage/config#Config.getOptional} */
getOptional<T = JsonValue>(key?: string): T | undefined {
return this.config.getOptional(key);
}
/** {@inheritdoc @backstage/config#Config.getConfig} */
getConfig(key: string): Config {
return this.config.getConfig(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalConfig} */
getOptionalConfig(key: string): Config | undefined {
return this.config.getOptionalConfig(key);
}
/** {@inheritdoc @backstage/config#Config.getConfigArray} */
getConfigArray(key: string): Config[] {
return this.config.getConfigArray(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalConfigArray} */
getOptionalConfigArray(key: string): Config[] | undefined {
return this.config.getOptionalConfigArray(key);
}
/** {@inheritdoc @backstage/config#Config.getNumber} */
getNumber(key: string): number {
return this.config.getNumber(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalNumber} */
getOptionalNumber(key: string): number | undefined {
return this.config.getOptionalNumber(key);
}
/** {@inheritdoc @backstage/config#Config.getBoolean} */
getBoolean(key: string): boolean {
return this.config.getBoolean(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalBoolean} */
getOptionalBoolean(key: string): boolean | undefined {
return this.config.getOptionalBoolean(key);
}
/** {@inheritdoc @backstage/config#Config.getString} */
getString(key: string): string {
return this.config.getString(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalString} */
getOptionalString(key: string): string | undefined {
return this.config.getOptionalString(key);
}
/** {@inheritdoc @backstage/config#Config.getStringArray} */
getStringArray(key: string): string[] {
return this.config.getStringArray(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalStringArray} */
getOptionalStringArray(key: string): string[] | undefined {
return this.config.getOptionalStringArray(key);
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { MockConfigApi } from './MockConfigApi';
@@ -15,5 +15,6 @@
*/
export * from './AnalyticsApi';
export * from './ConfigApi';
export * from './ErrorApi';
export * from './StorageApi';

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