Merge branch 'master' of https://github.com/backstage/backstage into pr-draft
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
The `subscribe` method on the `Config` returned by `loadBackendConfig` is now forwarded through `getConfig` and `getOptionalConfig`.
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Added the default `ScmAuth` implementation to the app.
|
||||
|
||||
To apply this change to an existing app, head to `packages/app/apis.ts`, import `ScmAuth` from `@backstage/integration-react`, and add a `ScmAuth.createDefaultApiFactory()` to your list of APIs:
|
||||
|
||||
```diff
|
||||
import {
|
||||
ScmIntegrationsApi,
|
||||
scmIntegrationsApiRef,
|
||||
+ ScmAuth,
|
||||
} from '@backstage/integration-react';
|
||||
|
||||
export const apis: AnyApiFactory[] = [
|
||||
...
|
||||
+ ScmAuth.createDefaultApiFactory(),
|
||||
...
|
||||
];
|
||||
```
|
||||
|
||||
If you have integrations towards SCM providers other than the default ones (github.com, gitlab.com, etc.), you will want to create a custom `ScmAuth` factory instead, for example like this:
|
||||
|
||||
```ts
|
||||
createApiFactory({
|
||||
api: scmAuthApiRef,
|
||||
deps: {
|
||||
gheAuthApi: gheAuthApiRef,
|
||||
githubAuthApi: githubAuthApiRef,
|
||||
},
|
||||
factory: ({ githubAuthApi, gheAuthApi }) =>
|
||||
ScmAuth.merge(
|
||||
ScmAuth.forGithub(githubAuthApi),
|
||||
ScmAuth.forGithub(gheAuthApi, {
|
||||
host: 'ghe.example.com',
|
||||
}),
|
||||
),
|
||||
});
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Fixed a bug where internal references within the catalog were broken when new entities where added through entity providers, such as registering a new location or adding one in configuration. These broken references then caused some entities to be incorrectly marked as orphaned and prevented refresh from working properly.
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-import': minor
|
||||
---
|
||||
|
||||
Switched to using the `ScmAuthApi` for authentication rather than GitHub auth. If you are instantiating your `CatalogImportClient` manually you now need to pass in an instance of `ScmAuthApi` instead.
|
||||
|
||||
Also be sure to register the `scmAuthApiRef` from the `@backstage/integration-react` in your app:
|
||||
|
||||
```ts
|
||||
import { ScmAuth } from '@backstage/integration-react';
|
||||
|
||||
// in packages/app/apis.ts
|
||||
|
||||
const apis = [
|
||||
// ... other APIs
|
||||
|
||||
ScmAuth.createDefaultApiFactory();
|
||||
|
||||
// OR
|
||||
|
||||
createApiFactory({
|
||||
api: scmAuthApiRef,
|
||||
deps: {
|
||||
gheAuthApi: gheAuthApiRef,
|
||||
githubAuthApi: githubAuthApiRef,
|
||||
},
|
||||
factory: ({ githubAuthApi, gheAuthApi }) =>
|
||||
ScmAuth.merge(
|
||||
ScmAuth.forGithub(githubAuthApi),
|
||||
ScmAuth.forGithub(gheAuthApi, {
|
||||
host: 'ghe.example.com',
|
||||
}),
|
||||
),
|
||||
});
|
||||
]
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
The `create-plugin` command now prefers dependency versions ranges that are already in the lockfile.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Support selective GitHub app installation for GHE
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Fix an issue where filtering in search doesn't work correctly for Bitbucket.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Added a check for the TechDocs annotation on the entity
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Stop forcing `target="_blank"` in the `SupportButton` but instead use the default logic of the `Link` component, that opens external targets in a new window and relative targets in the same window.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Fix duplication checks to stop looking for the old core packages, and to allow some explicitly
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
'@backstage/integration-react': patch
|
||||
---
|
||||
|
||||
Added `ScmAuthApi` along with the implementation `ScmAuth`. The `ScmAuthApi` provides methods for client-side authentication towards multiple different source code management services simultaneously.
|
||||
|
||||
When requesting credentials you supply a URL along with the same options as the other `OAuthApi`s, and optionally a request for additional high-level scopes.
|
||||
|
||||
For example like this:
|
||||
|
||||
```ts
|
||||
const { token } = await scmAuthApi.getCredentials({
|
||||
url: 'https://ghe.example.com/backstage/backstage',
|
||||
additionalScope: {
|
||||
repoWrite: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The instantiation of the API can either be done with a default factory that adds support for the public providers (github.com, gitlab.com, etc.):
|
||||
|
||||
```ts
|
||||
// in packages/app/apis.ts
|
||||
ScmAuth.createDefaultApiFactory();
|
||||
```
|
||||
|
||||
Or with a more custom setup that can add support for additional providers, for example like this:
|
||||
|
||||
```ts
|
||||
createApiFactory({
|
||||
api: scmAuthApiRef,
|
||||
deps: {
|
||||
gheAuthApi: gheAuthApiRef,
|
||||
githubAuthApi: githubAuthApiRef,
|
||||
},
|
||||
factory: ({ githubAuthApi, gheAuthApi }) =>
|
||||
ScmAuth.merge(
|
||||
ScmAuth.forGithub(githubAuthApi),
|
||||
ScmAuth.forGithub(gheAuthApi, {
|
||||
host: 'ghe.example.com',
|
||||
}),
|
||||
),
|
||||
});
|
||||
```
|
||||
|
||||
The additional `gheAuthApiRef` utility API can be defined either inside the app itself if it's only used for this purpose, for inside an internal common package for APIs, such as `@internal/apis`:
|
||||
|
||||
```ts
|
||||
const gheAuthApiRef: ApiRef<OAuthApi & ProfileInfoApi & SessionApi> =
|
||||
createApiRef({
|
||||
id: 'internal.auth.ghe',
|
||||
});
|
||||
```
|
||||
|
||||
And then implemented using the `GithubAuth` class from `@backstage/core-app-api`:
|
||||
|
||||
```ts
|
||||
createApiFactory({
|
||||
api: githubAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GithubAuth.create({
|
||||
provider: {
|
||||
id: 'ghe',
|
||||
icon: ...,
|
||||
title: 'GHE'
|
||||
},
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['read:user'],
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
Finally you also need to add and configure another GitHub provider to the `auth-backend` using the provider ID `ghe`:
|
||||
|
||||
```ts
|
||||
// Add the following options to `createRouter` in packages/backend/src/plugins/auth.ts
|
||||
providerFactories: {
|
||||
ghe: createGithubProvider(),
|
||||
},
|
||||
```
|
||||
|
||||
Other providers follow the same steps, but you will want to use the appropriate auth API implementation in the frontend, such as for example `GitlabAuth`.
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': minor
|
||||
---
|
||||
|
||||
Introduced a new `CatalogProcessorCache` that is available to catalog processors. It allows arbitrary values to be saved that will then be visible during the next run. The cache is scoped to each individual processor and entity, but is shared across processing steps in a single processor.
|
||||
|
||||
The cache is available as a new argument to each of the processing steps, except for `validateEntityKind` and `handleError`.
|
||||
|
||||
This also introduces an optional `getProcessorName` to the `CatalogProcessor` interface, which is used to provide a stable identifier for the processor. While it is currently optional it will move to be required in the future.
|
||||
|
||||
The breaking part of this change is the modification of the `state` field in the `EntityProcessingRequest` and `EntityProcessingResult` types. This is unlikely to have any impact as the `state` field was previously unused, but could require some minor updates.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
---
|
||||
|
||||
Added a check for the Kubernetes annotation on the entity
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Update OAuth refresh handler to pass updated refresh token to ensure cookie is updated with new value.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Update AboutCard to only render refresh button if the entity is managed by an url location.
|
||||
@@ -34,5 +34,22 @@ module.exports = {
|
||||
onNonMatchingHeader: 'replace',
|
||||
},
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
message:
|
||||
"Avoid using .toLowerCase(), use .toLocaleLowerCase('en-US') instead. " +
|
||||
'This rule can sometimes be ignored when converting text to be displayed to the user.',
|
||||
selector:
|
||||
"CallExpression[arguments.length=0] > MemberExpression[property.name='toLowerCase']",
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Avoid using .toUpperCase(), use .toLocaleUpperCase('en-US') instead. " +
|
||||
'This rule can sometimes be ignored when converting text to be displayed to the user.',
|
||||
selector:
|
||||
"CallExpression[arguments.length=0] > MemberExpression[property.name='toUpperCase']",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,3 +23,4 @@
|
||||
/.changeset/cost-insights-* @backstage/reviewers @backstage/silver-lining
|
||||
/.changeset/search-* @backstage/techdocs-core
|
||||
/.changeset/techdocs-* @backstage/techdocs-core
|
||||
/cypress/src/integration/plugins/techdocs.spec.ts @backstage/techdocs-core
|
||||
|
||||
@@ -38,6 +38,8 @@ labels: bug
|
||||
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
|
||||
<!-- ProTip: You can use `yarn backstage-cli info` command in your Backstage App for this section. -->
|
||||
|
||||
- NodeJS Version (v14):
|
||||
- Operating System and Version (e.g. Ubuntu 14.04):
|
||||
- Browser Information:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: documentation quality check
|
||||
uses: errata-ai/vale-action@v1.3.0
|
||||
uses: errata-ai/vale-action@v1.4.0
|
||||
# Whitelist excluding ADOPTERS, CHANGELOG and OWNERS (no exclude flag exists)
|
||||
with:
|
||||
files: '[".changeset", ".github", "contrib", "docs", "microsite", "packages", "plugins", "CONTRIBUTING.md", "CODE_OF_CONDUCT.md", "GOVERNANCE.md", "README.md"]'
|
||||
|
||||
@@ -133,3 +133,6 @@ site
|
||||
|
||||
# Sensitive credentials
|
||||
*-credentials.yaml
|
||||
|
||||
# e2e tests
|
||||
cypress/cypress/*
|
||||
|
||||
@@ -52,3 +52,5 @@
|
||||
| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. |
|
||||
| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe |
|
||||
| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe |
|
||||
| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. |
|
||||
| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. |
|
||||
|
||||
+37
-1
@@ -262,7 +262,9 @@ catalog:
|
||||
# Backstage example groups and users
|
||||
- type: file
|
||||
target: ../catalog-model/examples/acme-corp.yaml
|
||||
|
||||
# Backstage end-to-end tests of TechDocs
|
||||
- type: file
|
||||
target: ../../cypress/e2e-fixture.catalog.info.yaml
|
||||
scaffolder:
|
||||
# Use to customize default commit author info used when new components are created
|
||||
# defaultAuthor:
|
||||
@@ -358,6 +360,10 @@ auth:
|
||||
clientId: ${AUTH_ONELOGIN_CLIENT_ID}
|
||||
clientSecret: ${AUTH_ONELOGIN_CLIENT_SECRET}
|
||||
issuer: ${AUTH_ONELOGIN_ISSUER}
|
||||
bitbucket:
|
||||
development:
|
||||
clientId: ${AUTH_BITBUCKET_CLIENT_ID}
|
||||
clientSecret: ${AUTH_BITBUCKET_CLIENT_SECRET}
|
||||
costInsights:
|
||||
engineerCost: 200000
|
||||
products:
|
||||
@@ -382,6 +388,31 @@ costInsights:
|
||||
default: true
|
||||
MSC:
|
||||
name: Monthly Subscribers
|
||||
currencies:
|
||||
engineers:
|
||||
label: 'Engineers 🛠'
|
||||
unit: 'engineer'
|
||||
usd:
|
||||
label: 'US Dollars 💵'
|
||||
kind: 'USD'
|
||||
unit: 'dollar'
|
||||
prefix: '$'
|
||||
rate: 1
|
||||
carbonOffsetTons:
|
||||
label: 'Carbon Offset Tons ♻️⚖️s'
|
||||
kind: 'CARBON_OFFSET_TONS'
|
||||
unit: 'carbon offset ton'
|
||||
rate: 3.5
|
||||
beers:
|
||||
label: 'Beers 🍺'
|
||||
kind: 'BEERS'
|
||||
unit: 'beer'
|
||||
rate: 4.5
|
||||
pintsIceCream:
|
||||
label: 'Pints of Ice Cream 🍦'
|
||||
kind: 'PINTS_OF_ICE_CREAM'
|
||||
unit: 'ice cream pint'
|
||||
rate: 5.5
|
||||
homepage:
|
||||
clocks:
|
||||
- label: UTC
|
||||
@@ -400,3 +431,8 @@ jenkins:
|
||||
baseUrl: https://jenkins.example.com
|
||||
username: backstage-bot
|
||||
apiKey: 123456789abcdef0123456789abcedf012
|
||||
|
||||
azureDevOps:
|
||||
host: dev.azure.com
|
||||
token: my-token
|
||||
organization: my-company
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = {
|
||||
bundledDependencies: false,
|
||||
},
|
||||
],
|
||||
'jest/valid-expect': 'off',
|
||||
'jest/expect-expect': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
},
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
"baseUrl": "http://localhost:7000",
|
||||
"integrationFolder": "./src/integration",
|
||||
"supportFile": "./src/support",
|
||||
"fixturesFolder": "./src/fixures",
|
||||
"fixturesFolder": "./src/fixtures",
|
||||
"pluginsFile": "./src/plugins",
|
||||
"defaultCommandTimeout": 10000
|
||||
"defaultCommandTimeout": 10000,
|
||||
"viewportHeight": 900,
|
||||
"viewportWidth": 1440
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: techdocs-e2e-fixture
|
||||
description: Used for end-to-end tests of TechDocs in Backstage.
|
||||
annotations:
|
||||
backstage.io/techdocs-ref: dir:./fixtures
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: experimental
|
||||
owner: user:guest
|
||||
@@ -0,0 +1,3 @@
|
||||
# Home page
|
||||
|
||||
This is a basic documentation used for end-to-end tests.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Sub-page 1
|
||||
|
||||
## Section 1.1
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
## Section 1.2
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
## Section 1.3
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
@@ -0,0 +1,121 @@
|
||||
# Sub-page 3
|
||||
|
||||
## Section 3.1
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
## Section 3.2
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
### Sub-Section 3.2.1
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
### Sub-Section 3.2.2
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
## Section 3.3
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
@@ -0,0 +1,146 @@
|
||||
# Sub-page 2
|
||||
|
||||
## Section 2.1
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
## Section 2.2
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
### Sub-Section 2.2.1
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
### Sub-Section 2.2.2
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
## Section 2.3
|
||||
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
To next page!
|
||||
|
||||
[Link to Section 1.1](sub-page-one.md#section-11)
|
||||
@@ -0,0 +1,10 @@
|
||||
site_name: e2e Fixture Documentation
|
||||
site_description: Documentation used for end-to-end tests of TechDocs in Backstage.
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Sub-page 1: sub-page-one.md
|
||||
- Sub-page 2: sub-page-two.md
|
||||
- Nested Sub-pages:
|
||||
- Sub-page 3: sub-page-three.md
|
||||
plugins:
|
||||
- techdocs-core
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/// <reference types="cypress" />
|
||||
import 'os';
|
||||
|
||||
describe('TechDocs', () => {
|
||||
beforeEach(() => {
|
||||
cy.loginAsGuest();
|
||||
cy.mockSockJSNode();
|
||||
cy.interceptTechDocsAPICalls();
|
||||
});
|
||||
|
||||
describe('Navigating to TechDocs', () => {
|
||||
it('should navigate to the TechDocs home page via the primary navigation bar', () => {
|
||||
cy.visit('/');
|
||||
cy.wait(500);
|
||||
|
||||
cy.get('[data-testid="sidebar-root"]')
|
||||
.get('div')
|
||||
.get('a[href="/docs"]')
|
||||
.click();
|
||||
|
||||
cy.contains('Documentation');
|
||||
});
|
||||
|
||||
it('should navigate to the TechDocs home page from the URL', () => {
|
||||
cy.visit('/docs');
|
||||
cy.wait(500);
|
||||
|
||||
cy.contains('Documentation');
|
||||
});
|
||||
|
||||
it('should navigate to a specific TechDocs entity from the "Overview" tab', () => {
|
||||
cy.visit('/docs');
|
||||
cy.contains('techdocs-e2e-fixture')
|
||||
.parents()
|
||||
.eq(2)
|
||||
.contains('Read Docs')
|
||||
.click();
|
||||
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
'/docs/default/Component/techdocs-e2e-fixture',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to a specific TechDocs entity page from a URL', () => {
|
||||
cy.visit('/docs/default/Component/techdocs-e2e-fixture');
|
||||
cy.waitHomePage();
|
||||
|
||||
cy.contains('e2e Fixture Documentation');
|
||||
cy.contains(
|
||||
'Documentation used for end-to-end tests of TechDocs in Backstage.',
|
||||
);
|
||||
cy.getTechDocsShadowRoot().contains('Home page');
|
||||
});
|
||||
|
||||
it('should navigate to a specific TechDocs section from a URL', () => {
|
||||
cy.visit('/docs/default/Component/techdocs-e2e-fixture/sub-page-two');
|
||||
cy.waitSectionTwoPage();
|
||||
|
||||
cy.window().its('scrollY').should('equal', 0);
|
||||
|
||||
cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.contains('Sub-page 2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to a specific TechDocs fragment from a URL', () => {
|
||||
cy.visit(
|
||||
'/docs/default/Component/techdocs-e2e-fixture/sub-page-two#section-23',
|
||||
);
|
||||
cy.waitSectionTwoPage();
|
||||
|
||||
// This is used to test the post-render behavior of the techdocs Reader
|
||||
cy.wait(500);
|
||||
|
||||
cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.isInViewport('#section-23');
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to a wrong TechDocs entity page from a URL', () => {
|
||||
cy.visit('/docs/default/Component/wrong-component');
|
||||
|
||||
cy.get('[data-testid=error]').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigating within TechDocs', () => {
|
||||
it('should navigate to a specific TechDocs page via the navigation bar', () => {
|
||||
cy.visit('/docs/default/Component/techdocs-e2e-fixture');
|
||||
cy.waitHomePage();
|
||||
|
||||
cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.getTechDocsNavigation()
|
||||
.find('> div > div > [data-md-level="0"] > ul > li:nth-child(2) > a')
|
||||
.click();
|
||||
cy.contains('Sub-page 1');
|
||||
cy.window().its('scrollY').should('eq', 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigating within a TechDocs page', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/docs/default/Component/techdocs-e2e-fixture/sub-page-two');
|
||||
cy.waitSectionTwoPage();
|
||||
});
|
||||
it('should navigate to a specific fragment within the page via the table of contents - Level 1', () => {
|
||||
return cy.getTechDocsShadowRoot().within(() => {
|
||||
// Section 3
|
||||
cy.getTechDocsTableOfContents().within(() => {
|
||||
cy.get('> div > div > nav > ul > li:nth-child(3) > a').click();
|
||||
});
|
||||
|
||||
cy.isInViewport('#section-23');
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to a specific fragment within the page via the table of contents - Level 2', () => {
|
||||
return cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.isNotInViewport('#sub-section-222');
|
||||
// Section 2.2
|
||||
cy.getTechDocsTableOfContents()
|
||||
.find(
|
||||
'> div > div > nav > ul > li:nth-child(2) > nav > ul > li:nth-child(2) > a',
|
||||
)
|
||||
.click();
|
||||
|
||||
cy.isInViewport('#sub-section-222');
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to a specific TechDocs page fragment from a link', () => {
|
||||
return cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.get('.md-content > article')
|
||||
.contains('Link to Section 1.1')
|
||||
.click();
|
||||
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
'/docs/default/Component/techdocs-e2e-fixture/sub-page-one/',
|
||||
);
|
||||
expect(loc.hash).to.eq('#section-11');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to the next page within a TechDocs entity', () => {
|
||||
return cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.get('.md-footer-nav__link--next').click();
|
||||
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
'/docs/default/Component/techdocs-e2e-fixture/sub-page-three/',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to the previous page within a TechDocs entity', () => {
|
||||
return cy.getTechDocsShadowRoot().within(() => {
|
||||
cy.get('.md-footer-nav__link--prev').click();
|
||||
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
'/docs/default/Component/techdocs-e2e-fixture/sub-page-one/',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/// <reference types="cypress" />
|
||||
import 'os';
|
||||
|
||||
describe('Login', () => {
|
||||
it('should render the login page', () => {
|
||||
cy.visit('/');
|
||||
cy.contains('Select a sign-in method');
|
||||
});
|
||||
|
||||
it('should be able to login', () => {
|
||||
cy.get('button').contains('Enter').click();
|
||||
cy.url().should('include', '/catalog');
|
||||
|
||||
cy.contains('artist-lookup');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/// <reference types="cypress" />
|
||||
import 'os';
|
||||
|
||||
describe('Logout', () => {
|
||||
before(() => {
|
||||
cy.loginAsGuest();
|
||||
});
|
||||
it('should be able to logout', () => {
|
||||
cy.visit('/settings');
|
||||
cy.get('[data-testid="user-settings-menu"]').click();
|
||||
return cy
|
||||
.get('[data-testid="sign-out"]')
|
||||
.click()
|
||||
.then(() => {
|
||||
return expect(
|
||||
localStorage.getItem('@backstage/core:SignInPage:provider'),
|
||||
).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/* eslint-disable jest/no-standalone-expect */
|
||||
/// <reference types="cypress" />
|
||||
import 'os';
|
||||
|
||||
Cypress.Commands.add('loginAsGuest', () => {
|
||||
cy.visit('/', {
|
||||
onLoad: (win: Window) =>
|
||||
win.localStorage.setItem('@backstage/core:SignInPage:provider', 'guest'),
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getTechDocsShadowRoot', () => {
|
||||
cy.get('[data-testid="techdocs-content-shadowroot"]').shadow();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('isNotInViewport', element => {
|
||||
cy.get(element).then($el => {
|
||||
const bottom = Cypress.config(`viewportHeight`);
|
||||
const rect = $el[0].getBoundingClientRect();
|
||||
|
||||
if (bottom) {
|
||||
expect(rect.top).to.be.greaterThan(bottom);
|
||||
expect(rect.bottom).to.be.greaterThan(bottom);
|
||||
expect(rect.top).to.be.greaterThan(bottom);
|
||||
expect(rect.bottom).to.be.greaterThan(bottom);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('isInViewport', element => {
|
||||
cy.get(element).then($el => {
|
||||
const bottom = Cypress.config(`viewportHeight`);
|
||||
const rect = $el[0].getBoundingClientRect();
|
||||
|
||||
if (bottom) {
|
||||
expect(rect.top).not.to.be.greaterThan(bottom);
|
||||
expect(rect.bottom).not.to.be.greaterThan(bottom);
|
||||
expect(rect.top).not.to.be.greaterThan(bottom);
|
||||
expect(rect.bottom).not.to.be.greaterThan(bottom);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getTechDocsTableOfContents', () => {
|
||||
cy.get('[data-md-component="toc"]');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getTechDocsNavigation', () => {
|
||||
cy.get('[data-md-component="navigation"]');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('mockSockJSNode', () => {
|
||||
cy.intercept('GET', '**/sockjs-node/info**', {
|
||||
body: {
|
||||
websocket: true,
|
||||
origins: ['*:*'],
|
||||
cookie_needed: false,
|
||||
entropy: 2882389500,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('interceptTechDocsAPICalls', () => {
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'**/techdocs/metadata/entity/default/Component/techdocs-e2e-fixture',
|
||||
).as('entityMetadata');
|
||||
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'**/techdocs/metadata/techdocs/default/Component/techdocs-e2e-fixture',
|
||||
).as('techdocsMetadata');
|
||||
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'**/techdocs/sync/default/Component/techdocs-e2e-fixture',
|
||||
).as('syncEntity');
|
||||
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'**/techdocs/static/docs/default/Component/techdocs-e2e-fixture/sub-page-two/index.html',
|
||||
).as('sectionTwoHTML');
|
||||
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'**/techdocs/static/docs/default/Component/techdocs-e2e-fixture/index.html',
|
||||
).as('homeHTML');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('waitSectionTwoPage', () => {
|
||||
cy.wait([
|
||||
'@entityMetadata',
|
||||
'@syncEntity',
|
||||
'@techdocsMetadata',
|
||||
'@sectionTwoHTML',
|
||||
]);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('waitHomePage', () => {
|
||||
cy.wait(['@entityMetadata', '@syncEntity', '@techdocsMetadata', '@homeHTML']);
|
||||
});
|
||||
@@ -14,12 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference types="cypress" />
|
||||
|
||||
Cypress.Commands.add('loginAsGuest', () => {
|
||||
cy.visit('/', {
|
||||
onLoad: (win: Window) =>
|
||||
win.localStorage.setItem('@backstage/core:SignInPage:provider', 'guest'),
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
import './commands';
|
||||
|
||||
Vendored
+50
@@ -22,5 +22,55 @@ declare namespace Cypress {
|
||||
* @example cy.loginAsGuests
|
||||
*/
|
||||
loginAsGuest(): Chainable<Element>;
|
||||
/**
|
||||
* Get the TechDocs shadow root element
|
||||
* @example cy.getTechDocsShadowRoot
|
||||
*/
|
||||
getTechDocsShadowRoot(): Chainable<Element>;
|
||||
/**
|
||||
* Mock TechDocs backend API
|
||||
* @example cy.mockTechDocs
|
||||
*/
|
||||
mockTechDocs(): void;
|
||||
/**
|
||||
* Get the TechDocs table of contents element
|
||||
* @example cy.getTechDocsShadowRoot
|
||||
*/
|
||||
getTechDocsTableOfContents(): Chainable<Element>;
|
||||
/**
|
||||
* Get the TechDocs navigation element
|
||||
* @example cy.getTechDocsNavigation
|
||||
*/
|
||||
getTechDocsNavigation(): Chainable<Element>;
|
||||
/**
|
||||
* Intercept the TechDocs API calls
|
||||
* @example cy.interceptTechDocsAPICalls
|
||||
*/
|
||||
interceptTechDocsAPICalls(): Chainable<Element>;
|
||||
/**
|
||||
* Mock SockJS-Node call
|
||||
* @example cy.mockSockJSNode
|
||||
*/
|
||||
mockSockJSNode(): Chainable<Element>;
|
||||
/**
|
||||
* Wait TechDocs API response for home page
|
||||
* @example cy.waitHomePage
|
||||
*/
|
||||
waitHomePage(): Chainable<Element>;
|
||||
/**
|
||||
* Wait TechDocs API response for Section 2 page
|
||||
* @example cy.waitSectionTwoPage
|
||||
*/
|
||||
waitSectionTwoPage(): Chainable<Element>;
|
||||
/**
|
||||
* Check if the element is in viewport
|
||||
* @example cy.isInViewport
|
||||
*/
|
||||
isInViewport(element: string): Chainable<Element>;
|
||||
/**
|
||||
* Check if the element is not in viewport
|
||||
* @example cy.isNotInViewport
|
||||
*/
|
||||
isNotInViewport(element: string): Chainable<Element>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
id: provider
|
||||
title: Bitbucket Authentication Provider
|
||||
sidebar_label: Bitbucket
|
||||
description: Adding Bitbucket OAuth as an authentication provider in Backstage
|
||||
---
|
||||
|
||||
The Backstage `core-plugin-api` package comes with a Bitbucket authentication
|
||||
provider that can authenticate users using Bitbucket Cloud. This does **NOT**
|
||||
work with Bitbucket Server.
|
||||
|
||||
## Create an OAuth Consumer in Bitbucket
|
||||
|
||||
To add Bitbucket Cloud authentication, you must create an OAuth Consumer.
|
||||
|
||||
Go to `https://bitbucket.org/<your-project-name>/workspace/settings/api` .
|
||||
|
||||
Click Add Consumer.
|
||||
|
||||
Settings for local development:
|
||||
|
||||
- Application name: Backstage (or your custom app name)
|
||||
- Callback URL: `http://localhost:7000/api/auth/bitbucket`
|
||||
- Other are optional
|
||||
- (IMPORTANT) **Permissions: Account - Read, Workspace membership - Read**
|
||||
|
||||
## Configuration
|
||||
|
||||
The provider configuration can then be added to your `app-config.yaml` under the
|
||||
root `auth` configuration:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
environment: development
|
||||
providers:
|
||||
bitbucket:
|
||||
development:
|
||||
clientId: ${AUTH_BITBUCKET_CLIENT_ID}
|
||||
clientSecret: ${AUTH_BITBUCKET_CLIENT_SECRET}
|
||||
```
|
||||
|
||||
The Bitbucket provider is a structure with two configuration keys:
|
||||
|
||||
- `clientId`: The Key that you generated in Bitbucket, e.g.
|
||||
`b59241722e3c3b4816e2`
|
||||
- `clientSecret`: The Secret tied to the generated Key.
|
||||
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
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).
|
||||
@@ -50,6 +50,10 @@ The GitHub provider is a structure with three configuration keys:
|
||||
- `clientSecret`: The client secret tied to the generated client ID.
|
||||
- `enterpriseInstanceUrl` (optional): The base URL for a GitHub Enterprise
|
||||
instance, e.g. `https://ghe.<company>.com`. Only needed for GitHub Enterprise.
|
||||
- `callbackUrl` (optional): The callback url that GitHub will use when
|
||||
initiating an OAuth flow, e.g.
|
||||
`https://your-intermediate-service.com/handler`. Only needed if Backstage is
|
||||
not the immediate receiver (e.g. one OAuth app for many backstage instances).
|
||||
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
|
||||
+17
-1
@@ -57,6 +57,7 @@ postpack Restores the changes made by the prepack command
|
||||
|
||||
create-github-app Create new GitHub App in your organization (experimental)
|
||||
|
||||
info Show helpful information for debugging and reporting bugs
|
||||
help [command] display help for command
|
||||
```
|
||||
|
||||
@@ -423,7 +424,10 @@ This command uses a default Jest configuration that is included in the CLI,
|
||||
which is set up with similar goals for speed, scale, and working within a
|
||||
monorepo. The configuration sets the `src` as the root directory, enforces the
|
||||
`.test.` infix for tests, and uses `src/setupTests.ts` as the test setup
|
||||
location.
|
||||
location. The included configuration also supports test execution at the root of
|
||||
a yarn workspaces monorepo by automatically creating one grouped configuration
|
||||
that includes all packages that have `backstage-cli test` in their package
|
||||
`test` script.
|
||||
|
||||
If needed, the configuration can be extended using a `"jest"` field in
|
||||
`package.json`, both within the target package and the monorepo root, with
|
||||
@@ -647,3 +651,15 @@ YAML file that can be referenced in the GitHub integration configuration.
|
||||
```text
|
||||
Usage: backstage-cli create-github-app <github-org>
|
||||
```
|
||||
|
||||
## info
|
||||
|
||||
Scope: `root`
|
||||
|
||||
Outputs debug information which is useful when opening an issue. Outputs system
|
||||
information, node.js and npm versions, CLI version and type (inside backstage
|
||||
repo or a created app), all `@backstage/*` package dependency versions.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli info
|
||||
```
|
||||
|
||||
@@ -13,9 +13,10 @@ which during validation is stitched together into a single schema.
|
||||
## Schema Collection and Definition
|
||||
|
||||
Schemas are collected from all packages and dependencies in each repo that are a
|
||||
part of the Backstage ecosystem, including transitive dependencies. The current
|
||||
definition of "part of the ecosystem" is that a package has at least one
|
||||
dependency in the `@backstage` namespace, but this is subject to change.
|
||||
part of the Backstage ecosystem, including the root package and transitive
|
||||
dependencies. The current definition of "part of the ecosystem" is that a
|
||||
package has at least one dependency in the `@backstage` namespace or a
|
||||
`"configSchema"` field in `package.json`, but this is subject to change.
|
||||
|
||||
Each package is searched for a schema at a single point of entry, a top-level
|
||||
`"configSchema"` field in `package.json`. The field can either contain an
|
||||
|
||||
@@ -90,8 +90,7 @@ does so!
|
||||
## Creating a Catalog Data Reader Processor
|
||||
|
||||
The recommended way of instantiating the catalog backend classes is to use the
|
||||
[`CatalogBuilder`](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/src/service/CatalogBuilder.ts),
|
||||
as illustrated in the
|
||||
`CatalogBuilder`, as illustrated in the
|
||||
[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/catalog.ts).
|
||||
We will create a new
|
||||
[`CatalogProcessor`](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/src/ingestion/processors/types.ts)
|
||||
@@ -171,3 +170,105 @@ export default async function createPlugin(
|
||||
|
||||
Start up the backend - it should now start reading from the previously
|
||||
registered location and you'll see your entities start to appear in Backstage.
|
||||
|
||||
## Caching processing results
|
||||
|
||||
The catalog periodically refreshes entities in the catalog, and in doing so it
|
||||
calls out to external systems to fetch changes. This can be taxing for upstream
|
||||
services and large deployments may get rate limited if too many requests are
|
||||
sent. Luckily many external systems provide ETag support to check for changes
|
||||
which usually doesn't count towards the quota and saves resources both
|
||||
internally and externally.
|
||||
|
||||
The catalog has built in support for leveraging ETags when refreshing external
|
||||
locations in GitHub. This example aims to demonstrate how to add the same
|
||||
behavior for `system-x` that we implemented earlier.
|
||||
|
||||
```ts
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { Entity, LocationSpec } from '@backstage/catalog-model';
|
||||
import {
|
||||
results,
|
||||
CatalogProcessor,
|
||||
CatalogProcessorEmit,
|
||||
CatalogProcessorCache,
|
||||
CatalogProcessorParser,
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
|
||||
// It's recommended to always bump the CACHE_KEY version if you make
|
||||
// changes to the processor implementation or CacheItem.
|
||||
const CACHE_KEY = 'v1';
|
||||
|
||||
// Our cache item contains the ETag used in the upstream request
|
||||
// as well as the processing result used when the Etag matches.
|
||||
// Bump the CACHE_KEY version if you make any changes to this type.
|
||||
type CacheItem = {
|
||||
etag: string;
|
||||
entity: Entity;
|
||||
};
|
||||
|
||||
export class SystemXReaderProcessor implements CatalogProcessor {
|
||||
constructor(private readonly reader: UrlReader) {}
|
||||
|
||||
// It's recommended to give the processor a unique name.
|
||||
getProcessorName() {
|
||||
return 'system-x-processor';
|
||||
}
|
||||
|
||||
async readLocation(
|
||||
location: LocationSpec,
|
||||
_optional: boolean,
|
||||
emit: CatalogProcessorEmit,
|
||||
_parser: CatalogProcessorParser,
|
||||
cache: CatalogProcessorCache,
|
||||
): Promise<boolean> {
|
||||
// Pick a custom location type string. A location will be
|
||||
// registered later with this type.
|
||||
if (location.type !== 'system-x') {
|
||||
return false;
|
||||
}
|
||||
const cacheItem = await cache.get<CacheItem>(CACHE_KEY);
|
||||
try {
|
||||
// This assumes an URL reader that returns the response together with the ETag.
|
||||
// We send the ETag from the previous run if it exists.
|
||||
// The previous ETag will be set in the headers for the outgoing request and system-x
|
||||
// is going to throw NOT_MODIFIED (HTTP 304) if the ETag matches.
|
||||
const response = await this.reader.readUrl?.(location.target, {
|
||||
etag: cacheItem?.etag,
|
||||
});
|
||||
if (!response) {
|
||||
// readUrl is currently optional to implement so we have to check if we get a response back.
|
||||
throw new Error(
|
||||
'No URL reader that can parse system-x targets installed',
|
||||
);
|
||||
}
|
||||
|
||||
// ETag is optional in the response but we need it to cache the result.
|
||||
if (!response.etag) {
|
||||
throw new Error(
|
||||
'No ETag returned from system-x, cannot use response for caching',
|
||||
);
|
||||
}
|
||||
|
||||
// For this example the JSON payload is a single entity.
|
||||
const entity: Entity = JSON.parse(response.buffer.toString());
|
||||
emit(results.entity(location, entity));
|
||||
|
||||
// Update the cache with the new ETag and entity used for the next run.
|
||||
await cache.set<CacheItem>(CACHE_KEY, {
|
||||
etag: response.etag,
|
||||
entity,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.name === 'NotModifiedError' && cacheItem) {
|
||||
// The ETag matches and we have a cached value from the previous run.
|
||||
emit(results.entity(location, cacheItem.entity));
|
||||
}
|
||||
const message = `Unable to read ${location.type}, ${error}`;
|
||||
emit(results.generalError(location, message));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -6,7 +6,7 @@ description: How to write your own actions
|
||||
|
||||
If you're wanting to extend the functionality of the Scaffolder, you can do so
|
||||
by writing custom actions which can be used along side our
|
||||
[built-in actions](./builtin-actions.md)
|
||||
[built-in actions](./builtin-actions.md).
|
||||
|
||||
### Writing your Custom Action
|
||||
|
||||
@@ -60,7 +60,7 @@ close over the `TemplateAction`. Take a look at our
|
||||
[built-in actions](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/src/scaffolder/actions/builtin)
|
||||
for reference.
|
||||
|
||||
We set the type generic to `{ contents: string, filename: string}` which is
|
||||
We set the type generic to `{ contents: string, filename: string }` which is
|
||||
there to set the type on the handler `ctx` `inputs` property so we get good type
|
||||
checking. This could be generated from the next part of this guide, the `input`
|
||||
schema, but it's not supported right now. Feel free to contribute 🚀 👍.
|
||||
@@ -68,12 +68,12 @@ schema, but it's not supported right now. Feel free to contribute 🚀 👍.
|
||||
The `createTemplateAction` takes an object which specifies the following:
|
||||
|
||||
- `id` - a unique ID for your custom action. We encourage you to namespace these
|
||||
in someway so they wont collide with future built-in actions that we may ship
|
||||
with the `scaffolder-backend` plugin.
|
||||
in some way so that they won't collide with future built-in actions that we
|
||||
may ship with the `scaffolder-backend` plugin.
|
||||
- `schema.input` - A JSON schema for input values to your function
|
||||
- `schema.output` - A JSON schema for values which are outputted from the
|
||||
function using `ctx.output`
|
||||
- `handler` the actual code which is run part of the action, with a context.
|
||||
- `handler` - the actual code which is run part of the action, with a context
|
||||
|
||||
#### The context object
|
||||
|
||||
@@ -81,7 +81,7 @@ When the action `handler` is called, we provide you a `context` as the only
|
||||
argument. It looks like the following:
|
||||
|
||||
- `ctx.baseUrl` - a string where the template is located
|
||||
- `ctx.logger` - a winston logger for additional logging inside your action
|
||||
- `ctx.logger` - a Winston logger for additional logging inside your action
|
||||
- `ctx.logStream` - a stream version of the logger if needed
|
||||
- `ctx.workspacePath` - a string of the working directory of the template run
|
||||
- `ctx.input` - an object which should match the JSON schema provided in the
|
||||
|
||||
@@ -97,7 +97,7 @@ spec:
|
||||
entityRef: '{{ steps.register.output.entityRef }}'
|
||||
```
|
||||
|
||||
Let's dive in an pick apart what each of these sections do and what they are.
|
||||
Let's dive in and pick apart what each of these sections do and what they are.
|
||||
|
||||
### `spec.parameters` - `FormStep | FormStep[]`
|
||||
|
||||
@@ -256,11 +256,11 @@ use `ui:widget: password` or set some properties of `ui:backstage`:
|
||||
|
||||
#### The Repository Picker
|
||||
|
||||
So in order to make working with repository providers easier, we've built a
|
||||
custom picker that can be used by overriding the `ui:field` option in the
|
||||
`uiSchema` for a `string` field. Instead of displaying a text input block it
|
||||
will render our custom component that we've built which makes it easy to select
|
||||
a repository provider, and insert a project or owner, and repository name.
|
||||
In order to make working with repository providers easier, we've built a custom
|
||||
picker that can be used by overriding the `ui:field` option in the `uiSchema`
|
||||
for a `string` field. Instead of displaying a text input block it will render
|
||||
our custom component that we've built which makes it easy to select a repository
|
||||
provider, and insert a project or owner, and repository name.
|
||||
|
||||
You can see it in the above full example which is a separate step and it looks a
|
||||
little like this:
|
||||
@@ -323,9 +323,9 @@ template. These follow the same standard format:
|
||||
name: '{{ parameters.name }}'
|
||||
```
|
||||
|
||||
By default we ship some built in actions that you can take a look at
|
||||
[here](./builtin-actions.md), or you can create your own custom actions by
|
||||
looking at the docs [here](./writing-custom-actions.md)
|
||||
By default we ship some [built in actions](./builtin-actions.md) that you can
|
||||
take a look at, or you can
|
||||
[create your own custom actions](./writing-custom-actions.md).
|
||||
|
||||
### Outputs
|
||||
|
||||
@@ -359,5 +359,5 @@ As you can see above in the `Outputs` section, `actions` and `steps` can also
|
||||
output things. You can grab that output using `steps.$stepId.output.$property`.
|
||||
|
||||
You can read more about all the `inputs` and `outputs` defined in the actions in
|
||||
code part of the `JSONSchema`, or you can read more about our built in ones
|
||||
[here](./builtin-actions.md).
|
||||
code part of the `JSONSchema`, or you can read more about our
|
||||
[built in actions](./builtin-actions.md).
|
||||
|
||||
@@ -142,6 +142,84 @@ const AppRoutes = () => {
|
||||
};
|
||||
```
|
||||
|
||||
## How to customize the TechDocs reader page?
|
||||
|
||||
Similar to how it is possible to customize the TechDocs Home, it is also
|
||||
possible to customize the TechDocs Reader Page. It is done in your `app`
|
||||
package. By default, you might see something like this in your `App.tsx`:
|
||||
|
||||
```tsx
|
||||
const AppRoutes = () => {
|
||||
<Route path="/docs/:namespace/:kind/:name/*" element={<TechDocsReaderPage />}>
|
||||
{techDocsPage}
|
||||
</Route>;
|
||||
};
|
||||
```
|
||||
|
||||
The `techDocsPage` is a default techdocs reader page which lives in
|
||||
`packages/app/src/components/techdocs`. It includes the following without you
|
||||
having to set anything up.
|
||||
|
||||
```tsx
|
||||
<TechDocsPage>
|
||||
{({ techdocsMetadataValue, entityMetadataValue, entityRef, onReady }) => (
|
||||
<>
|
||||
<TechDocsPageHeader
|
||||
techDocsMetadata={techdocsMetadataValue}
|
||||
entityMetadata={entityMetadataValue}
|
||||
entityRef={entityRef}
|
||||
/>
|
||||
<Content data-testid="techdocs-content">
|
||||
<Reader onReady={onReady} entityRef={entityRef} />
|
||||
</Content>
|
||||
</>
|
||||
)}
|
||||
</TechDocsPage>
|
||||
```
|
||||
|
||||
If you would like to compose your own `techDocsPage`, you can do so by replacing
|
||||
the children of TechDocsPage with something else. Maybe you are _just_
|
||||
interested in replacing the Header:
|
||||
|
||||
```tsx
|
||||
<TechDocsPage>
|
||||
{({ entityRef, onReady }) => (
|
||||
<>
|
||||
<Header type="documentation" title="Custom Header" />
|
||||
<Content data-testid="techdocs-content">
|
||||
<Reader onReady={onReady} entityRef={entityRef} />
|
||||
</Content>
|
||||
</>
|
||||
)}
|
||||
</TechDocsPage>
|
||||
```
|
||||
|
||||
Or maybe you want to disable the in-context search
|
||||
|
||||
```tsx
|
||||
<TechDocsPage>
|
||||
{({ entityRef, onReady }) => (
|
||||
<>
|
||||
<Header type="documentation" title="Custom Header" />
|
||||
<Content data-testid="techdocs-content">
|
||||
<Reader onReady={onReady} entityRef={entityRef} withSearch={false} />
|
||||
</Content>
|
||||
</>
|
||||
)}
|
||||
</TechDocsPage>
|
||||
```
|
||||
|
||||
Or maybe you want to replace the entire TechDocs Page.
|
||||
|
||||
```tsx
|
||||
<TechDocsPage>
|
||||
<Header type="documentation" title="Custom Header" />
|
||||
<Content data-testid="techdocs-content">
|
||||
<p>my own content</p>
|
||||
</Content>
|
||||
</TechDocsPage>
|
||||
```
|
||||
|
||||
## How to migrate from TechDocs Alpha to Beta
|
||||
|
||||
> This guide only applies to the "recommended" TechDocs deployment method (where
|
||||
|
||||
@@ -37,9 +37,7 @@ exported by [@backstage/theme](https://www.npmjs.com/package/@backstage/theme)
|
||||
in combination with
|
||||
[createTheme](https://material-ui.com/customization/theming/#createmuitheme-options-args-theme)
|
||||
from [@material-ui/core](https://www.npmjs.com/package/@material-ui/core). See
|
||||
the
|
||||
[@backstage/theme source](https://github.com/backstage/backstage/tree/master/packages/theme/src)
|
||||
and the implementation of the `createTheme` function for how this is done.
|
||||
the "Overriding Backstage and Material UI css rules" section below.
|
||||
|
||||
You can also create a theme from scratch that matches the `BackstageTheme` type
|
||||
exported by [@backstage/theme](https://www.npmjs.com/package/@backstage/theme).
|
||||
@@ -136,6 +134,79 @@ const themeOptions = createThemeOptions({
|
||||
});
|
||||
```
|
||||
|
||||
## Overriding Backstage and Material UI components styles
|
||||
|
||||
When creating a custom theme you would be applying different values to
|
||||
component's css rules that use the theme object. For example, a Backstage
|
||||
component's styles might look like this:
|
||||
|
||||
```ts
|
||||
const useStyles = makeStyles<BackstageTheme>(
|
||||
theme => ({
|
||||
header: {
|
||||
padding: theme.spacing(3),
|
||||
boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)',
|
||||
backgroundImage: theme.page.backgroundImage,
|
||||
},
|
||||
}),
|
||||
{ name: 'BackstageHeader' },
|
||||
);
|
||||
```
|
||||
|
||||
Notice how the `padding` is getting its value from `theme.spacing`, that means
|
||||
that setting a value for spacing in your custom theme would affect this
|
||||
component padding property and the same goes for `backgroundImage` which uses
|
||||
`theme.page.backgroundImage`. However, the `boxShadow` property doesn't
|
||||
reference any value from the theme, that means that creating a custom theme
|
||||
wouldn't be enough to alter the `box-shadow` property or to add css rules that
|
||||
aren't already defined like a margin. For these cases you should also create an
|
||||
override.
|
||||
|
||||
```ts
|
||||
import { createApp } from '@backstage/core-app-api';
|
||||
import { BackstageTheme, lightTheme } from '@backstage/theme';
|
||||
/**
|
||||
* The `@backstage/core-components` package exposes this type that
|
||||
* contains all Backstage and `material-ui` components that can be
|
||||
* overridden along with the classes key those components use.
|
||||
*/
|
||||
import { BackstageOverrides } from '@backstage/core-components';
|
||||
|
||||
export const createCustomThemeOverrides = (
|
||||
theme: BackstageTheme,
|
||||
): BackstageOverrides => {
|
||||
return {
|
||||
BackstageHeader: {
|
||||
header: {
|
||||
width: 'auto',
|
||||
margin: '20px',
|
||||
boxShadow: 'none',
|
||||
borderBottom: `4px solid ${theme.palette.primary.main}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const app = createApp({
|
||||
apis: ...,
|
||||
plugins: ...,
|
||||
themes: [{
|
||||
id: 'my-theme',
|
||||
title: 'My Custom Theme',
|
||||
variant: 'light',
|
||||
theme: {
|
||||
...lightTheme,
|
||||
overrides: {
|
||||
// These are the overrides that Backstage applies to `material-ui` components
|
||||
...lightTheme.overrides,
|
||||
// These are your custom overrides, either to `material-ui` or Backstage components.
|
||||
...createCustomThemeOverrides(lightTheme),
|
||||
},
|
||||
},
|
||||
}]
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Logo
|
||||
|
||||
In addition to a custom theme, you can also customize the logo displayed at the
|
||||
|
||||
@@ -27,7 +27,7 @@ point building on top of the previous one:
|
||||
and the new APIs can be used in parallel. This deprecation must have been
|
||||
released for at least two weeks before the deprecated API is removed in a
|
||||
minor version bump.
|
||||
- **3** - The time limit for the deprecation is 3 months instead of two weeks.
|
||||
- **3** - The time limit for the deprecation is 3 months instead of two days.
|
||||
|
||||
TL;DR:
|
||||
|
||||
@@ -279,7 +279,7 @@ Integrates GraphiQL as a tool to browse GraphQL API endpoints inside Backstage.
|
||||
|
||||
Stability: `1`
|
||||
|
||||
### `graphql` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/graphql/)
|
||||
### `graphql` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/graphql-backend/)
|
||||
|
||||
A backend plugin that provides
|
||||
|
||||
|
||||
@@ -1,42 +1,39 @@
|
||||
---
|
||||
id: composability
|
||||
title: Composability System Migration
|
||||
title: Composability System
|
||||
# prettier-ignore
|
||||
description: Documentation and migration instructions for new composability APIs.
|
||||
description: Documentation for the Backstage plugin composability APIs.
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This page describes the new composability system that was recently introduced in
|
||||
Backstage, and it does so from the perspective of the existing patterns and
|
||||
APIs. As the new system is solidified and existing code is ported, this page
|
||||
will be removed and replaced with a more direct description of the composability
|
||||
system. For now, the primary purpose of this documentation is to aid in the
|
||||
migration of existing plugins, but it does cover the migration of apps as well.
|
||||
This page describes the composability system that helps bring together content
|
||||
from a multitude of plugins into one Backstage application.
|
||||
|
||||
The core principle of the new composability system is that plugins should have
|
||||
clear boundaries and connections. It should isolate crashes within a plugin, but
|
||||
allow navigation between them. It should allow for plugins to be loaded only
|
||||
when needed, and enable plugins to provide extension points for other plugins to
|
||||
The core principle of the composability system is that plugins should have clear
|
||||
boundaries and connections. It should isolate crashes within a plugin, but allow
|
||||
navigation between them. It should allow for plugins to be loaded only when
|
||||
needed, and enable plugins to provide extension points for other plugins to
|
||||
build upon. The composability system is also built with an app-first mindset,
|
||||
prioritizing simplicity and clarity in the app over that in the plugins and core
|
||||
APIs.
|
||||
|
||||
The new composability system isn't a single new API surface. It is a collection
|
||||
of patterns, primitives, new APIs, and old APIs used in new ways. At the core is
|
||||
the new concept of extensions, which are exported by plugins for use in the app.
|
||||
There is also a new primitive called component data, which assists in the
|
||||
conversion to a more declarative app. The `RouteRef`s now have a clear purpose
|
||||
as well, and can be used route to pages in a flexible way.
|
||||
The composability system isn't a single API surface. It is a collection of
|
||||
patterns, primitives, and APIs. At the core is the concept of **extensions**,
|
||||
which are exported by plugins for use in the app. There is also a primitive
|
||||
called component data, which helps keep the structure of the app more
|
||||
declarative. There are also `RouteRef`s that help route between pages in a
|
||||
flexible way, which is especially important when bringing together different
|
||||
open source plugins.
|
||||
|
||||
## New Concepts
|
||||
## Concepts
|
||||
|
||||
This section is a brief look into all the new and updated concepts that were put
|
||||
in place to support the new composability system.
|
||||
This section is a brief look into all the concepts that help support the
|
||||
composability system.
|
||||
|
||||
### Component Data
|
||||
|
||||
Component data is a new composability primitive that is introduced as a way to
|
||||
Component data is a composability primitive that is introduced as a way to
|
||||
provide a new data dimension for React components. Data is attached to React
|
||||
components using a key, and is then readable from any JSX elements created with
|
||||
those components, using the same key, as illustrated by the following example:
|
||||
@@ -59,7 +56,7 @@ inspected, while our component data adds more structured access and simplifies
|
||||
evolution by allowing for multiple different versions of a piece of data to be
|
||||
used and interpreted at once.
|
||||
|
||||
The initial use-case for component data is to support route and plugin discovery
|
||||
One of the use-cases for component data is to support route and plugin discovery
|
||||
through elements in the app. Through this we allow for the React element tree in
|
||||
the app to be the source of truth, both for which plugins are used, as well as
|
||||
all top-level plugin routes in the app. The use of component data is not limited
|
||||
@@ -101,9 +98,10 @@ supply a `RouteRef` as `mountPoint`. The mount point will be the handle of the
|
||||
component for the outside world, and is used by other components and plugins
|
||||
that wish to link to the routable component.
|
||||
|
||||
As of now there are only two extension creation functions, but it is possible to
|
||||
add more of them in the future, both in the core library and in plugins that
|
||||
wish to provide an extension point for other plugins to build upon. Extensions
|
||||
As of now there are only two extension creation functions in the core library,
|
||||
but more may be added in the future. There are also some plugins that provide
|
||||
ways to extend functionality through their own extensions, like
|
||||
`createScaffolderFieldExtension` from `@backstage/plugin-scaffolder`. Extensions
|
||||
are also not tied to React, and can both be used to model generic JavaScript
|
||||
concepts, as well as potentially bridge to rendering libraries and web
|
||||
frameworks other than React.
|
||||
@@ -192,14 +190,30 @@ const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### New Routing System
|
||||
### Naming Patterns
|
||||
|
||||
A big piece of what is enabled by moving over to this new composability system
|
||||
is to make `RouteRef`s useful. The `RouteRef`s no longer have their own path, in
|
||||
fact the only required parameter is currently a `title`. Instead of assigning a
|
||||
path to each `RouteRef` and possibly overriding these paths in the app, the
|
||||
concrete `path` for each `RouteRef` is discovered based on the element tree in
|
||||
the app. Let's consider the following example:
|
||||
There are a couple of naming patters to adhere to as you build plugins, which
|
||||
helps clarify the intent and usage of the exports.
|
||||
|
||||
| Description | Pattern | Examples |
|
||||
| --------------------- | --------------- | ---------------------------------------------- |
|
||||
| Top-level Pages | \*Page | CatalogIndexPage, SettingsPage, LighthousePage |
|
||||
| Entity Tab Content | Entity\*Content | EntityJenkinsContent, EntityKubernetesContent |
|
||||
| Entity Overview Card | Entity\*Card | EntitySentryCard, EntityPagerDutyCard |
|
||||
| Entity Conditional | is\*Available | isPagerDutyAvailable, isJenkinsAvailable |
|
||||
| Plugin Instance | \*Plugin | jenkinsPlugin, catalogPlugin |
|
||||
| Utility API Reference | \*ApiRef | configApiRef, catalogApiRef |
|
||||
|
||||
### Routing System
|
||||
|
||||
The routing system of Backstage relies heavily on the composability system. It
|
||||
uses `RouteRef`s to represent routing targets in the app, which at runtime will
|
||||
be bound to a concrete `path`, but provides a level of indirection to help mix
|
||||
together different plugins that otherwise wouldn't know how to route to each
|
||||
other.
|
||||
|
||||
The concrete `path` for each `RouteRef` is discovered based on the element tree
|
||||
in the app. Let's consider the following example:
|
||||
|
||||
```tsx
|
||||
const appRoutes = (
|
||||
@@ -216,12 +230,10 @@ extension it has a `RouteRef` assigned as its mount point, which we'll refer to
|
||||
as `fooPageRouteRef`.
|
||||
|
||||
Given the above example, the `fooPageRouteRef` will be associated with the
|
||||
`'/foo'` route. The path is no longer accessible via the `path` property of the
|
||||
`RouteRef` though, as the routing structure is tied to the app's react tree. We
|
||||
instead use the new `useRouteRef` hook if we want to create a concrete link to
|
||||
the page. The `useRouteRef` hook takes a single `RouteRef` as its only
|
||||
parameter, and returns a function that is called to create the URL. For example
|
||||
like this:
|
||||
`'/foo'` route. If we want to route to the `FooPage`, we can use the
|
||||
`useRouteRef` hook to create a concrete link to the page. The `useRouteRef` hook
|
||||
takes a single `RouteRef` as its only parameter, and returns a function that is
|
||||
called to create the URL. For example like this:
|
||||
|
||||
```tsx
|
||||
const MyComponent = () => {
|
||||
@@ -230,16 +242,15 @@ const MyComponent = () => {
|
||||
};
|
||||
```
|
||||
|
||||
Now let's assume that we want to link from the `BarPage` to the `FooPage`.
|
||||
Before the introduction of the new composability system, we would do this by
|
||||
importing the `fooPageRouteRef` exported by the `fooPlugin`. This created an
|
||||
unnecessary dependency on the plugin, and also provided little flexibility in
|
||||
allowing the app to tie plugins together, with the links instead being dictated
|
||||
by the plugins themselves. To solve this, we introduce `ExternalRouteRef`s. Much
|
||||
like regular route references, they can be passed to `useRouteRef` to create
|
||||
concrete URLs, but they can not be used as mount points in routable component
|
||||
and instead have to be associated with a target route using route bindings in
|
||||
the app.
|
||||
Now let's assume that we want to link from the `BarPage` to the `FooPage`. We
|
||||
don't want to reference the `fooPageRouteRef` directly from our `barPlugin`,
|
||||
since that would create an unnecessary dependency on the `fooPlugin`. It would
|
||||
also provided little flexibility in allowing the app to tie plugins together,
|
||||
with the links instead being dictated by the plugins themselves. To solve this,
|
||||
we use `ExternalRouteRef`s. Much like regular route references, they can be
|
||||
passed to `useRouteRef` to create concrete URLs, but they can not be used as
|
||||
mount points in routable component and instead have to be associated with a
|
||||
target route using route bindings in the app.
|
||||
|
||||
We create a new `ExternalRouteRef` inside the `barPlugin`, using a neutral name
|
||||
that describes its role in the plugin rather than a specific plugin page that it
|
||||
@@ -305,6 +316,14 @@ in a different file than the one that creates the plugin instance, for example a
|
||||
top-level `routes.ts`. This is to avoid circular imports when you use the route
|
||||
references from other parts of the same plugin.
|
||||
|
||||
Another thing to note is that this indirection in the routing is particularly
|
||||
useful for open source plugins that need to leave flexibility in how they are
|
||||
integrated. For plugins that you build internally for your own Backstage
|
||||
application, you can choose to go the route of direct imports or even use
|
||||
concrete routes directly. Although there can be some benefits to using the full
|
||||
routing system even in internal plugins. It can help you structure your routes,
|
||||
and as you will see further down it also helps you manage route parameters.
|
||||
|
||||
### Optional External Routes
|
||||
|
||||
When creating an `ExternalRouteRef` it is possible to mark it as optional:
|
||||
@@ -338,7 +357,7 @@ const MyComponent = () => {
|
||||
|
||||
### Parameterized Routes
|
||||
|
||||
A new addition to `RouteRef`s is the possibility of adding named and typed
|
||||
A feature of `RouteRef`s is the possibility of adding named and typed
|
||||
parameters. Parameters are declared at creation, and will enforce presence of
|
||||
the parameters in the path in the app, and require them as a parameter when
|
||||
using `useRouteRef`.
|
||||
@@ -415,21 +434,12 @@ const MyPage = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### New Catalog Components
|
||||
### Catalog Components
|
||||
|
||||
The established pattern for selecting what plugins should be available on each
|
||||
catalog page is to use custom components in the app, with logic embedded in the
|
||||
render function. Typically this takes form as a component that either receives
|
||||
the entity via props or uses the `useEntity` hook to retrieve the selected
|
||||
entity. A `switch` or `if` / `else if` chain is then used to select what
|
||||
children should be rendered based on information in the entity.
|
||||
|
||||
This pattern will no longer work with the new composability system, and in
|
||||
general is very difficult to build any form of declarative model around, as it
|
||||
depends on runtime execution. To help replace existing code, a new
|
||||
`EntitySwitch` component has been added to the `@backstage/catalog` plugin,
|
||||
which grabs the selected entity from a context, and selects at most one element
|
||||
to render using a list of `EntitySwitch.Case` children.
|
||||
To help structure the catalog entity pages in your app and choose what content
|
||||
to render in different scenarios, the `@backstage/catalog` plugin provides an
|
||||
`EntitySwitch` component. It works by selecting at most one element to render
|
||||
using a list of `EntitySwitch.Case` children.
|
||||
|
||||
For example, if you want all entities of kind `"Template"` to be rendered with a
|
||||
`MyTemplate` component, and all other entities to be rendered with a `MyOther`
|
||||
@@ -475,6 +485,9 @@ new `EntityLayout` component. It is a tweaked version and replacement for the
|
||||
`EntityPageLayout` component, and is introduced more in depth in the app
|
||||
migration section below.
|
||||
|
||||
**NOTE**: The rest of this documentation covers how to migrate older
|
||||
applications to the new composability system described above.
|
||||
|
||||
## Porting Existing Plugins
|
||||
|
||||
There are a couple of high-level steps to porting an existing plugin to the new
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
---
|
||||
id: using-backstage-proxy-within-plugin
|
||||
title: Using the Backstage Proxy from Within a Plugin
|
||||
# prettier-ignore
|
||||
description: Guide on how to create a set of API bindings that interface with a backend via the backstage proxy
|
||||
---
|
||||
|
||||
This guide walks you through setting up a simple proxy to an existing API that
|
||||
is deployed externally to backstage and sending requests to that API from within
|
||||
a backstage frontend plugin.
|
||||
|
||||
If your plugin requires access to an API, backstage offers
|
||||
[3 options](../plugins/call-existing-api.md):
|
||||
|
||||
1. you can
|
||||
[access the API directly](../plugins/call-existing-api.md#issuing-requests-directly),
|
||||
1. you can create a [backend plugin](../plugins/backend-plugin.md) if you are
|
||||
implementing the API alongside your frontend plugin
|
||||
1. you can configure backstage to proxy to an already existing API.
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
- [Setting up the backstage proxy](#setting-up-the-backstage-proxy)
|
||||
- [Calling an API using the backstage proxy](#calling-an-api-using-the-backstage-proxy)
|
||||
- [Defining the API client interface](#defining-the-api-client-interface)
|
||||
- [Creating the API client](#creating-the-api-client)
|
||||
- [Bundling your ApiRef with your plugin](#bundling-your-apiref-with-your-plugin)
|
||||
- [Using the API in your components](#using-your-plugin-in-your-components)
|
||||
|
||||
# Setting up the backstage proxy
|
||||
|
||||
Let's say your plugin's API is hosted at _https://api.myawesomeservice.com/v1_,
|
||||
and you want to be able to access it within backstage at
|
||||
`/api/proxy/<your-proxy-uri>`, and add a default header called
|
||||
`X-Custom-Source`. You will need to add the following to `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
proxy:
|
||||
'/<your-proxy-uri>':
|
||||
target: https://api.myawesomeservice.com/v1
|
||||
headers:
|
||||
X-Custom-Source: backstage
|
||||
```
|
||||
|
||||
You can find more details about the proxy config options in the
|
||||
[proxying section](../plugins/proxying.md).
|
||||
|
||||
# Calling an API using the backstage proxy
|
||||
|
||||
If you followed the previous steps, you should now be able to access your API by
|
||||
calling `${backend-url}/api/proxy/<your-proxy-uri>`. The reason why
|
||||
`backend-url` is referenced is because the backstage backend creates and runs
|
||||
the proxy. Backstage is structured in such a way that you could run the
|
||||
backstage frontend independently of the backend. So when calling your API you
|
||||
need to prepend the backend url to your http call.
|
||||
|
||||
The recommended pattern for calling out to services is to wrap your calls in a
|
||||
[Utility API](../api/utility-apis.md). This section describes the steps to wrap
|
||||
your API client in a Utility API, which are:
|
||||
|
||||
- use [`createApiRef`](../reference/core-plugin-api.createapiref.md) to create a
|
||||
new [`ApiRef`](../reference/core-plugin-api.apiref.md)
|
||||
- register an [`ApiFactory`](../reference/core-plugin-api.apifactory.md) with
|
||||
your plugin using
|
||||
[`createApiFactory`](../reference/core-plugin-api.createapifactory.md). This
|
||||
will wrap your API implementation, associate your `ApiRef` with your
|
||||
implementation and tell backstage how to instantiate it
|
||||
- finally, you can use your API in your components by calling
|
||||
[`useApi`](../reference/core-plugin-api.useapi.md)
|
||||
|
||||
## Defining the API client interface
|
||||
|
||||
Continuing from the previous example, let's assume that
|
||||
_https://api.myawesomeservice.com/v1_ has the following endpoints:
|
||||
|
||||
| Method | Description |
|
||||
| :----------------------- | :---------------------- |
|
||||
| `GET /users` | Returns a list of users |
|
||||
| `GET /users/{userId}` | Returns a single user |
|
||||
| `DELETE /users/{userId}` | Deletes a user |
|
||||
|
||||
Here is an example definition for this API following backstage's `apiRef` style:
|
||||
|
||||
```ts
|
||||
/* src/api.ts */
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export interface User {
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface MyAwesomeApi {
|
||||
url: string;
|
||||
listUsers: () => Promise<List<User>>;
|
||||
getUser: (userId: string) => Promise<User>;
|
||||
deleteUser: (userId: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const myAwesomeApiRef = createApiRef<MyAwesomeApi>({
|
||||
id: 'plugin.my-awesome-api.service',
|
||||
description: 'Example API definition',
|
||||
});
|
||||
```
|
||||
|
||||
## Creating the API client
|
||||
|
||||
The `myAwesomeApiRef` is what you will use within backstage to reference the API
|
||||
client in your plugin. The API ref itself is a global singleton object that
|
||||
allows you to reference your instantiated API. The actual implementation would
|
||||
look something like this:
|
||||
|
||||
```ts
|
||||
/* src/api.ts */
|
||||
|
||||
/* ... */
|
||||
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
|
||||
export class MyAwesomeApiClient implements MyAwesomeApi {
|
||||
discoveryApi: DiscoveryApi;
|
||||
|
||||
constructor({discoveryApi}: {discoveryApi: DiscoveryApi}) {
|
||||
this.discoveryApi = discoveryApi;
|
||||
}
|
||||
|
||||
private async fetch<T = any>(input: string, init?: RequestInit): Promise<T> {
|
||||
// As configured previously for the backend proxy
|
||||
const proxyUri = '${await this.discoveryApi.getBaseUrl('proxy')}/<your-proxy-uri>';
|
||||
|
||||
const resp = await fetch(`${proxyUri}${input}`, init);
|
||||
if (!resp.ok) throw new Error(resp);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
async listUsers(): Promise<List<User>> {
|
||||
return await this.fetch<List<User>>('/users');
|
||||
}
|
||||
|
||||
async getUser(userId: string): Promise<User> {
|
||||
return await this.fetch<User>(`/users/${userId}`);
|
||||
}
|
||||
|
||||
async deleteUser(userId: string): Promise<boolean> {
|
||||
return await this.fetch<boolean>(
|
||||
`/users/${userId}`,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
> For more information on the DiscoveryApi check out the
|
||||
> [docs](../reference/core-plugin-api.discoveryapi.md)
|
||||
|
||||
## Bundling your ApiRef with your plugin
|
||||
|
||||
The final piece in the puzzle is bundling the `myAwesomeApiRef` with a factory
|
||||
for `MyAwesomeApiClient` objects. This is usually done in the `plugin.ts` file
|
||||
inside the plugin's `src` directory. This is an example of what it'd look like,
|
||||
assuming you added the previous code in a file called `api.ts`:
|
||||
|
||||
```ts
|
||||
/* src/plugin.ts */
|
||||
import { myAwesomeApiRef, MyAwesomeApiClient } from './api';
|
||||
import {
|
||||
createPlugin,
|
||||
createRouteRef,
|
||||
createApiFactory,
|
||||
createRoutableExtension,
|
||||
createComponentExtension,
|
||||
discoveryApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
//...
|
||||
|
||||
export const myCustomPlugin = createPlugin({
|
||||
id: '<your-plugin-name>',
|
||||
|
||||
// Configure a factory for myAwesomeApiRef
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: myAwesomeApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef },
|
||||
factory: ({ discoveryApi }) => new MyAwesomeApiClient({ discoveryApi }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Using the API in your components
|
||||
|
||||
Now you should be able to access your API using the backstage hook
|
||||
[`useApi`](../reference/core-plugin-api.useapi.md) from within your plugin code.
|
||||
|
||||
```ts
|
||||
/* plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx */
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { myAwesomeApiRef } from '../../api';
|
||||
|
||||
export const AwesomeUsersTable = () => {
|
||||
const apiClient = useApi(myAwesomeApiRef);
|
||||
|
||||
apiClient.listUsers()
|
||||
.then(
|
||||
...
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: The Big 5-0
|
||||
author: Francesco Corti, Spotify
|
||||
authorURL: https://github.com/fcorti/
|
||||
authorImageURL: https://avatars.githubusercontent.com/u/6010860?v=4
|
||||
---
|
||||
|
||||
We’re excited to celebrate an important milestone for the Backstage community: 50+ [public adopters](https://github.com/backstage/backstage/blob/master/ADOPTERS.md)!
|
||||
|
||||

|
||||
|
||||
Before digging into why we believe this is so important, we want to send a huge “thank you” to all the Backstage adopters and contributors who have helped grow the Backstage community. First, to the publicly listed adopters as your visible support for the project excites others to learn more about Backstage. Second, to those non-listed adopters, many of whom still engage in the community via issues, comments, or code contributions. Thank you!
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## Why we care about public adopters
|
||||
|
||||

|
||||
|
||||
If you roll the clock back to early 2020 when Backstage was open sourced, we never could’ve imagined surpassing this milestone. It feels particularly big given that Backstage tends to be adopted by complex organizations with hundreds (if not thousands of) developers and thousands (if not tens of thousands) of software components.
|
||||
|
||||
We’ve also seen such diverse examples of Backstage in the wild. Adopters as varied as [American Airlines][am] and [Splunk][sp] have demoed their Backstage-built developer portals. We’ve seen digital-first companies like [Zalando][za] and [DAZN][da] share their journey from proof-of-concept to in-production. And the developer experience team at [Expedia Group][ex] has been shared at multiple Community Sessions, detailing their adoption journey as well as contributing back with ideas for new features.
|
||||
|
||||
For the Backstage project, public adopters are important because they provide a wide variety of use cases, industries, and degrees of complexity (teams, number of services, etc.) that Backstage is able to support. In addition, a growing adopters list shows the project’s maturity and helps other organizations understand the benefits and make the decision to join the project and the community.
|
||||
|
||||
In other words, the more the public adopters list grows and becomes more diverse, the more the community will grow and provide contributions that benefit all adopters.
|
||||
|
||||
## Fifty is just the beginning
|
||||
|
||||

|
||||
|
||||
While we’re thrilled to celebrate this milestone, the public adopters list is just one metric we monitor in the overall health of the Backstage project. It definitely doesn’t tell the whole story! We’re seeing continued growth within the contributor community, PRs, and GitHub overall ratings as well. As fate would have it, we also recently reached 50+ plugins in the [Backstage Plugin Marketplace][plugins]!
|
||||
|
||||
## Join us!
|
||||
|
||||
If you are a Backstage enthusiast, please [join me][news] and the entire Backstage Team in celebrating this milestone. And if you are a Backstage adopter not already listed in the [GitHub page][gh], consider adding your name to better inform the community and participate in the project.
|
||||
|
||||
[am]: https://backstage.spotify.com/blog/adopter-spotlight/american-airlines-runway/
|
||||
[sp]: https://backstage.spotify.com/blog/community-session/splunk-pink-phonebook/
|
||||
[za]: https://youtu.be/6sg5uMCLxTA
|
||||
[da]: https://medium.com/dazn-tech/developer-experience-dx-at-dazn-e6de9a0208d2
|
||||
[ex]: https://backstage.spotify.com/blog/community-session/firehydrant-expedia-loblaw/
|
||||
[plugins]: https://backstage.io/plugins
|
||||
[news]: https://mailchi.mp/spotify/backstage-community
|
||||
[gh]: https://github.com/backstage/backstage/blob/master/ADOPTERS.md
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Badges
|
||||
author: Andreas Stenius
|
||||
authorUrl: https://github.com/backstage/community
|
||||
category: Discovery
|
||||
description: The badges plugin offers a set of badges that can be used outside of Backstage, showing information related to data from the catalog.
|
||||
documentation: https://github.com/backstage/backstage/blob/master/plugins/badges/README.md
|
||||
iconUrl: img/badges.svg
|
||||
npmPackageName: '@backstage/plugin-badges'
|
||||
@@ -3,7 +3,7 @@ title: GitHub Release Manager
|
||||
author: '@Spotify'
|
||||
authorUrl: https://github.com/spotify
|
||||
category: Release management
|
||||
description: Manage releases without having to juggle git commands
|
||||
description: Manage releases without having to juggle git commands.
|
||||
documentation: https://github.com/backstage/backstage/tree/master/plugins/git-release-manager
|
||||
iconUrl: img/git-release-manager-logo.svg
|
||||
npmPackageName: '@backstage/plugin-git-release-manager'
|
||||
|
||||
@@ -3,7 +3,7 @@ title: GKE Usage
|
||||
author: BESTSELLER
|
||||
authorUrl: bestsellerit.com
|
||||
category: Discovery
|
||||
description: This plugin will show you the cost and resource usage of your application within GKE.
|
||||
description: This plugin will show you the cost and resource usage of your application within Google Kubernetes Engine (GKE).
|
||||
documentation: https://github.com/BESTSELLER/backstage-plugin-gkeusage/blob/master/README.md
|
||||
iconUrl: https://bestsellerit.com/img/google-container-engine_avatar.svg
|
||||
npmPackageName: '@bestsellerit/backstage-plugin-gkeusage'
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Harbor
|
||||
author: BESTSELLER
|
||||
authorUrl: bestsellerit.com
|
||||
category: Discovery
|
||||
description: This plugin will show you information about docker images within harbor.
|
||||
description: This plugin will show you information about Docker images within the Harbor cloud native registry.
|
||||
documentation: https://github.com/BESTSELLER/backstage-plugin-harbor/blob/master/README.md
|
||||
iconUrl: https://bestsellerit.com/img/terraform-harbor/goharbor.jpeg
|
||||
npmPackageName: '@bestsellerit/backstage-plugin-harbor'
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Prometheus
|
||||
author: Roadie
|
||||
authorUrl: https://roadie.io
|
||||
category: Monitoring
|
||||
description: Prometheus plugin provides visualization of Prometheus metrics and alerts
|
||||
documentation: https://roadie.io/backstage/plugins/prometheus/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=prometheus
|
||||
iconUrl: https://avatars.githubusercontent.com/u/3380462?s=200&v=4
|
||||
npmPackageName: '@roadiehq/backstage-plugin-prometheus'
|
||||
tags:
|
||||
- monitoring
|
||||
- graphs
|
||||
- alerting
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Shortcuts
|
||||
author: Spotify
|
||||
authorUrl: https://github.com/spotify
|
||||
category: Utility
|
||||
description: The shortcuts plugin allows a user to have easy access to pages within a Backstage app by storing them as "shortcuts" in the Sidebar.
|
||||
documentation: https://github.com/backstage/backstage/blob/master/plugins/shortcuts/README.md
|
||||
iconUrl: img/shortcuts.svg
|
||||
npmPackageName: '@backstage/plugin-shortcuts'
|
||||
@@ -20,6 +20,20 @@ class Index extends React.Component {
|
||||
|
||||
return (
|
||||
<main className="MainContent">
|
||||
<Banner.Container>
|
||||
<Banner.Dismissable storageKey="2021-10-07-software-templates-techdocs-beta">
|
||||
🚀 Feature updates!{' '}
|
||||
<a href="https://backstage.io/blog/2021/07/26/software-templates-are-now-in-beta">
|
||||
Software Templates
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a href="https://backstage.io/blog/2021/09/16/the-techdocs-beta-has-landed">
|
||||
TechDocs
|
||||
</a>{' '}
|
||||
are now in beta.
|
||||
</Banner.Dismissable>
|
||||
</Banner.Container>
|
||||
|
||||
<Block small className="bg-black-grey stripe-bottom">
|
||||
<Block.Container>
|
||||
<Block.TextBox>
|
||||
@@ -54,15 +68,6 @@ class Index extends React.Component {
|
||||
</Block.Container>
|
||||
</Block>
|
||||
|
||||
<Banner.Container>
|
||||
<Banner.Dismissable storageKey="k8s-launch">
|
||||
🎉 New feature: Kubernetes for service owners.{' '}
|
||||
<a href="https://backstage.io/blog/2021/01/12/new-backstage-feature-kubernetes-for-service-owners">
|
||||
Learn more.
|
||||
</a>
|
||||
</Banner.Dismissable>
|
||||
</Banner.Container>
|
||||
|
||||
<Block small className="stripe-top bg-black">
|
||||
<Block.Container wrapped>
|
||||
<Block.TextBox>
|
||||
|
||||
@@ -255,7 +255,8 @@
|
||||
"tutorials/quickstart-app-plugin",
|
||||
"tutorials/migrating-away-from-core",
|
||||
"tutorials/configuring-plugin-databases",
|
||||
"tutorials/switching-sqlite-postgres"
|
||||
"tutorials/switching-sqlite-postgres",
|
||||
"tutorials/using-backstage-proxy-within-plugin"
|
||||
],
|
||||
"Architecture Decision Records (ADRs)": [
|
||||
"architecture-decisions/adrs-overview",
|
||||
|
||||
@@ -1094,10 +1094,27 @@ code {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
z-index: 100;
|
||||
|
||||
margin: 25px auto 0 auto;
|
||||
max-width: 1430px;
|
||||
height: 0;
|
||||
margin: -14px auto 14px auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.Banner__Container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 645px) {
|
||||
.Banner__Container {
|
||||
margin-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 335px) {
|
||||
.Banner__Container {
|
||||
margin-top: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
.Banner__DismissButton {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3v-3h18v3z"/></svg>
|
||||
|
After Width: | Height: | Size: 251 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><g><rect fill="none" height="24" width="24"/></g><g><path d="M14,10H3v2h11V10z M14,6H3v2h11V6z M18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2H18z M3,16h7v-2H3V16z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 298 B |
+3
-1
@@ -111,7 +111,7 @@ nav:
|
||||
- Structure of a plugin: 'plugins/structure-of-a-plugin.md'
|
||||
- Plugin Development: 'plugins/plugin-development.md'
|
||||
- Integrate into the Software Catalog: 'plugins/integrating-plugin-into-software-catalog.md'
|
||||
- Composability System Migration: 'plugins/composability.md'
|
||||
- Composability System: 'plugins/composability.md'
|
||||
- Backends and APIs:
|
||||
- Proxying: 'plugins/proxying.md'
|
||||
- Backend plugin: 'plugins/backend-plugin.md'
|
||||
@@ -140,6 +140,7 @@ nav:
|
||||
- Google: 'auth/google/provider.md'
|
||||
- Okta: 'auth/okta/provider.md'
|
||||
- OneLogin: 'auth/onelogin/provider.md'
|
||||
- Bitbucket: 'auth/bitbucket/provider.md'
|
||||
- Adding authentication providers: 'auth/add-auth-provider.md'
|
||||
- Using authentication and identity: 'auth/using-auth.md'
|
||||
- Sign in resolvers: 'auth/identity-resolver.md'
|
||||
@@ -165,6 +166,7 @@ nav:
|
||||
- Migrating away from @backstage/core: 'tutorials/migrating-away-from-core.md'
|
||||
- Adding Custom Plugin to Existing Monorepo App: 'tutorials/quickstart-app-plugin.md'
|
||||
- Switching Backstage from SQLite to PostgreSQL: 'tutorials/switching-sqlite-postgres.md'
|
||||
- Using the Backstage Proxy from Within a Plugin: 'tutorials/using-backstage-proxy-within-plugin.md'
|
||||
- Architecture Decision Records (ADRs):
|
||||
- Overview: 'architecture-decisions/index.md'
|
||||
- ADR001 - Architecture Decision Record (ADR) log: 'architecture-decisions/adr001-add-adr-log.md'
|
||||
|
||||
+2
-2
@@ -13,10 +13,10 @@
|
||||
"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",
|
||||
"tsc:full": "tsc --skipLibCheck false --incremental false",
|
||||
"tsc:full": "backstage-cli clean && tsc --skipLibCheck false --incremental false",
|
||||
"clean": "backstage-cli clean && lerna run clean",
|
||||
"diff": "lerna run diff --",
|
||||
"test": "lerna run test --since origin/master -- --coverage",
|
||||
"test": "backstage-cli test",
|
||||
"test:all": "lerna run test -- --coverage",
|
||||
"lint": "lerna run lint --since origin/master --",
|
||||
"lint:docs": "node ./scripts/check-docs-quality",
|
||||
|
||||
@@ -1,5 +1,89 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/cli@0.7.15
|
||||
- @backstage/core-components@0.6.1
|
||||
- @backstage/core-plugin-api@0.1.10
|
||||
- @backstage/core-app-api@0.1.16
|
||||
- @backstage/plugin-org@0.3.26
|
||||
- @backstage/plugin-catalog@0.7.0
|
||||
- @backstage/plugin-catalog-react@0.5.2
|
||||
- @backstage/catalog-model@0.9.4
|
||||
- @backstage/plugin-cost-insights@0.11.9
|
||||
- @backstage/plugin-user-settings@0.3.8
|
||||
- @backstage/plugin-kubernetes@0.4.16
|
||||
- @backstage/plugin-catalog-import@0.7.1
|
||||
- @backstage/plugin-badges@0.2.12
|
||||
- @backstage/plugin-home@0.4.3
|
||||
- @backstage/plugin-search@0.4.14
|
||||
- @backstage/plugin-shortcuts@0.1.11
|
||||
- @backstage/plugin-api-docs@0.6.11
|
||||
- @backstage/plugin-catalog-graph@0.1.3
|
||||
- @backstage/plugin-circleci@0.2.26
|
||||
- @backstage/plugin-cloudbuild@0.2.26
|
||||
- @backstage/plugin-code-coverage@0.1.14
|
||||
- @backstage/plugin-explore@0.3.19
|
||||
- @backstage/plugin-gcp-projects@0.3.7
|
||||
- @backstage/plugin-github-actions@0.4.21
|
||||
- @backstage/plugin-graphiql@0.2.19
|
||||
- @backstage/plugin-jenkins@0.5.9
|
||||
- @backstage/plugin-kafka@0.2.18
|
||||
- @backstage/plugin-lighthouse@0.2.28
|
||||
- @backstage/plugin-newrelic@0.3.7
|
||||
- @backstage/plugin-pagerduty@0.3.16
|
||||
- @backstage/plugin-rollbar@0.3.17
|
||||
- @backstage/plugin-scaffolder@0.11.7
|
||||
- @backstage/plugin-sentry@0.3.24
|
||||
- @backstage/plugin-tech-radar@0.4.10
|
||||
- @backstage/plugin-techdocs@0.12.1
|
||||
- @backstage/plugin-todo@0.1.13
|
||||
|
||||
## 0.2.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/cli@0.7.14
|
||||
- @backstage/plugin-techdocs@0.12.0
|
||||
- @backstage/plugin-user-settings@0.3.7
|
||||
- @backstage/core-app-api@0.1.15
|
||||
- @backstage/plugin-catalog-import@0.7.0
|
||||
- @backstage/plugin-badges@0.2.11
|
||||
- @backstage/plugin-cost-insights@0.11.8
|
||||
- @backstage/plugin-tech-radar@0.4.9
|
||||
- @backstage/core-plugin-api@0.1.9
|
||||
- @backstage/plugin-kubernetes@0.4.15
|
||||
- @backstage/core-components@0.6.0
|
||||
- @backstage/integration-react@0.1.11
|
||||
- @backstage/plugin-catalog@0.6.17
|
||||
- @backstage/plugin-api-docs@0.6.10
|
||||
- @backstage/plugin-catalog-graph@0.1.2
|
||||
- @backstage/plugin-catalog-react@0.5.1
|
||||
- @backstage/plugin-circleci@0.2.25
|
||||
- @backstage/plugin-cloudbuild@0.2.25
|
||||
- @backstage/plugin-code-coverage@0.1.13
|
||||
- @backstage/plugin-explore@0.3.18
|
||||
- @backstage/plugin-gcp-projects@0.3.6
|
||||
- @backstage/plugin-github-actions@0.4.20
|
||||
- @backstage/plugin-graphiql@0.2.18
|
||||
- @backstage/plugin-home@0.4.2
|
||||
- @backstage/plugin-jenkins@0.5.8
|
||||
- @backstage/plugin-kafka@0.2.17
|
||||
- @backstage/plugin-lighthouse@0.2.27
|
||||
- @backstage/plugin-newrelic@0.3.6
|
||||
- @backstage/plugin-org@0.3.25
|
||||
- @backstage/plugin-pagerduty@0.3.15
|
||||
- @backstage/plugin-rollbar@0.3.16
|
||||
- @backstage/plugin-scaffolder@0.11.6
|
||||
- @backstage/plugin-search@0.4.13
|
||||
- @backstage/plugin-sentry@0.3.23
|
||||
- @backstage/plugin-shortcuts@0.1.10
|
||||
- @backstage/plugin-todo@0.1.12
|
||||
|
||||
## 0.2.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+39
-39
@@ -1,46 +1,46 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.47",
|
||||
"version": "0.2.49",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.3",
|
||||
"@backstage/cli": "^0.7.13",
|
||||
"@backstage/core-app-api": "^0.1.14",
|
||||
"@backstage/core-components": "^0.5.0",
|
||||
"@backstage/core-plugin-api": "^0.1.8",
|
||||
"@backstage/integration-react": "^0.1.10",
|
||||
"@backstage/plugin-api-docs": "^0.6.9",
|
||||
"@backstage/plugin-badges": "^0.2.10",
|
||||
"@backstage/plugin-catalog": "^0.6.16",
|
||||
"@backstage/plugin-catalog-graph": "^0.1.1",
|
||||
"@backstage/plugin-catalog-import": "^0.6.0",
|
||||
"@backstage/plugin-catalog-react": "^0.5.0",
|
||||
"@backstage/plugin-circleci": "^0.2.24",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.24",
|
||||
"@backstage/plugin-code-coverage": "^0.1.12",
|
||||
"@backstage/plugin-cost-insights": "^0.11.7",
|
||||
"@backstage/plugin-explore": "^0.3.17",
|
||||
"@backstage/plugin-gcp-projects": "^0.3.5",
|
||||
"@backstage/plugin-github-actions": "^0.4.19",
|
||||
"@backstage/plugin-graphiql": "^0.2.17",
|
||||
"@backstage/plugin-home": "^0.4.1",
|
||||
"@backstage/plugin-jenkins": "^0.5.7",
|
||||
"@backstage/plugin-kafka": "^0.2.16",
|
||||
"@backstage/plugin-kubernetes": "^0.4.14",
|
||||
"@backstage/plugin-lighthouse": "^0.2.26",
|
||||
"@backstage/plugin-newrelic": "^0.3.5",
|
||||
"@backstage/plugin-org": "^0.3.24",
|
||||
"@backstage/plugin-pagerduty": "0.3.14",
|
||||
"@backstage/plugin-rollbar": "^0.3.15",
|
||||
"@backstage/plugin-scaffolder": "^0.11.5",
|
||||
"@backstage/plugin-search": "^0.4.12",
|
||||
"@backstage/plugin-sentry": "^0.3.22",
|
||||
"@backstage/plugin-shortcuts": "^0.1.9",
|
||||
"@backstage/plugin-tech-radar": "^0.4.8",
|
||||
"@backstage/plugin-techdocs": "^0.11.3",
|
||||
"@backstage/plugin-todo": "^0.1.11",
|
||||
"@backstage/plugin-user-settings": "^0.3.6",
|
||||
"@backstage/catalog-model": "^0.9.4",
|
||||
"@backstage/cli": "^0.7.15",
|
||||
"@backstage/core-app-api": "^0.1.16",
|
||||
"@backstage/core-components": "^0.6.1",
|
||||
"@backstage/core-plugin-api": "^0.1.10",
|
||||
"@backstage/integration-react": "^0.1.11",
|
||||
"@backstage/plugin-api-docs": "^0.6.11",
|
||||
"@backstage/plugin-badges": "^0.2.12",
|
||||
"@backstage/plugin-catalog": "^0.7.0",
|
||||
"@backstage/plugin-catalog-graph": "^0.1.3",
|
||||
"@backstage/plugin-catalog-import": "^0.7.1",
|
||||
"@backstage/plugin-catalog-react": "^0.5.2",
|
||||
"@backstage/plugin-circleci": "^0.2.26",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.26",
|
||||
"@backstage/plugin-code-coverage": "^0.1.14",
|
||||
"@backstage/plugin-cost-insights": "^0.11.9",
|
||||
"@backstage/plugin-explore": "^0.3.19",
|
||||
"@backstage/plugin-gcp-projects": "^0.3.7",
|
||||
"@backstage/plugin-github-actions": "^0.4.21",
|
||||
"@backstage/plugin-graphiql": "^0.2.19",
|
||||
"@backstage/plugin-home": "^0.4.3",
|
||||
"@backstage/plugin-jenkins": "^0.5.9",
|
||||
"@backstage/plugin-kafka": "^0.2.18",
|
||||
"@backstage/plugin-kubernetes": "^0.4.16",
|
||||
"@backstage/plugin-lighthouse": "^0.2.28",
|
||||
"@backstage/plugin-newrelic": "^0.3.7",
|
||||
"@backstage/plugin-org": "^0.3.26",
|
||||
"@backstage/plugin-pagerduty": "0.3.16",
|
||||
"@backstage/plugin-rollbar": "^0.3.17",
|
||||
"@backstage/plugin-scaffolder": "^0.11.7",
|
||||
"@backstage/plugin-search": "^0.4.14",
|
||||
"@backstage/plugin-sentry": "^0.3.24",
|
||||
"@backstage/plugin-shortcuts": "^0.1.11",
|
||||
"@backstage/plugin-tech-radar": "^0.4.10",
|
||||
"@backstage/plugin-techdocs": "^0.12.1",
|
||||
"@backstage/plugin-todo": "^0.1.13",
|
||||
"@backstage/plugin-user-settings": "^0.3.8",
|
||||
"@backstage/search-common": "^0.2.0",
|
||||
"@backstage/theme": "^0.2.10",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
@@ -62,7 +62,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.18",
|
||||
"@rjsf/core": "^3.0.0",
|
||||
"@testing-library/cypress": "^7.0.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -84,6 +84,8 @@ import { searchPage } from './components/search/SearchPage';
|
||||
import { providers } from './identityProviders';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
import { techDocsPage } from './components/techdocs/TechDocsPage';
|
||||
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
@@ -170,7 +172,9 @@ const routes = (
|
||||
<Route
|
||||
path="/docs/:namespace/:kind/:name/*"
|
||||
element={<TechDocsReaderPage />}
|
||||
/>
|
||||
>
|
||||
{techDocsPage}
|
||||
</Route>
|
||||
<Route path="/create" element={<ScaffolderPage />}>
|
||||
<ScaffolderFieldExtensions>
|
||||
<LowerCaseValuePickerFieldExtension />
|
||||
|
||||
@@ -26,7 +26,7 @@ export const LowerCaseValuePickerFieldExtension = scaffolderPlugin.provide(
|
||||
name: 'LowerCaseValuePicker',
|
||||
component: TextValuePicker,
|
||||
validation: (value: string, validation: FieldValidation) => {
|
||||
if (value.toLowerCase() !== value) {
|
||||
if (value.toLocaleLowerCase('en-US') !== value) {
|
||||
validation.addError('Only lowercase values are allowed.');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Content } from '@backstage/core-components';
|
||||
import {
|
||||
TechDocsPageHeader,
|
||||
TechDocsPage,
|
||||
Reader,
|
||||
} from '@backstage/plugin-techdocs';
|
||||
import React from 'react';
|
||||
|
||||
const DefaultTechDocsPage = () => {
|
||||
return (
|
||||
<TechDocsPage>
|
||||
{({ techdocsMetadataValue, entityMetadataValue, entityRef, onReady }) => (
|
||||
<>
|
||||
<TechDocsPageHeader
|
||||
techDocsMetadata={techdocsMetadataValue}
|
||||
entityMetadata={entityMetadataValue}
|
||||
entityRef={entityRef}
|
||||
/>
|
||||
<Content data-testid="techdocs-content">
|
||||
<Reader onReady={onReady} entityRef={entityRef} />
|
||||
</Content>
|
||||
</>
|
||||
)}
|
||||
</TechDocsPage>
|
||||
);
|
||||
};
|
||||
|
||||
export const techDocsPage = <DefaultTechDocsPage />;
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
oneloginAuthApiRef,
|
||||
oauth2ApiRef,
|
||||
oidcAuthApiRef,
|
||||
bitbucketAuthApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export const providers = [
|
||||
@@ -81,4 +82,10 @@ export const providers = [
|
||||
message: 'Sign In using OneLogin',
|
||||
apiRef: oneloginAuthApiRef,
|
||||
},
|
||||
{
|
||||
id: 'bitbucket-auth-provider',
|
||||
title: 'Bitbucket',
|
||||
message: 'Sign In using Bitbucket',
|
||||
apiRef: bitbucketAuthApiRef,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.9.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8f969d5a56: Correct error message typo
|
||||
- a31afc5b62: Replace slash stripping regexp with trimEnd to remove CodeQL warning
|
||||
- d7055285de: Add glob patterns support to config CORS options. It's possible to send patterns like:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
cors:
|
||||
origin:
|
||||
- https://*.my-domain.com
|
||||
- http://localhost:700[0-9]
|
||||
- https://sub-domain-+([0-9]).my-domain.com
|
||||
```
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/config-loader@0.6.10
|
||||
- @backstage/integration@0.6.7
|
||||
- @backstage/cli-common@0.1.4
|
||||
|
||||
## 0.9.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8bb3c0a578: The `subscribe` method on the `Config` returned by `loadBackendConfig` is now forwarded through `getConfig` and `getOptionalConfig`.
|
||||
- 0c8a59e293: Fix an issue where filtering in search doesn't work correctly for Bitbucket.
|
||||
- Updated dependencies
|
||||
- @backstage/integration@0.6.6
|
||||
- @backstage/config-loader@0.6.9
|
||||
|
||||
## 0.9.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.9.4",
|
||||
"version": "0.9.6",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -29,11 +29,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.3",
|
||||
"@backstage/cli-common": "^0.1.4",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/config-loader": "^0.6.8",
|
||||
"@backstage/config-loader": "^0.6.10",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@backstage/integration": "^0.6.5",
|
||||
"@backstage/integration": "^0.6.7",
|
||||
"@google-cloud/storage": "^5.8.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/cors": "^2.8.6",
|
||||
@@ -77,8 +77,8 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.13",
|
||||
"@backstage/test-utils": "^0.1.17",
|
||||
"@backstage/cli": "^0.7.15",
|
||||
"@backstage/test-utils": "^0.1.18",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/concat-stream": "^1.6.0",
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from '@backstage/integration';
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { trimEnd } from 'lodash';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import {
|
||||
@@ -149,7 +150,7 @@ export class BitbucketUrlReader implements UrlReader {
|
||||
// a future improvement, we could be smart and try to deduce that non-glob
|
||||
// prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used
|
||||
// to get just that part of the repo.
|
||||
const treeUrl = url.replace(filepath, '').replace(/\/+$/, '');
|
||||
const treeUrl = trimEnd(url.replace(filepath, ''), '/');
|
||||
|
||||
const tree = await this.readTree(treeUrl, {
|
||||
etag: options?.etag,
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
ReadUrlResponse,
|
||||
ReadUrlOptions,
|
||||
} from './types';
|
||||
import { trimEnd } from 'lodash';
|
||||
|
||||
/** @public */
|
||||
export class GitlabUrlReader implements UrlReader {
|
||||
@@ -186,7 +187,7 @@ export class GitlabUrlReader implements UrlReader {
|
||||
// a future improvement, we could be smart and try to deduce that non-glob
|
||||
// prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used
|
||||
// to get just that part of the repo.
|
||||
const treeUrl = url.replace(filepath, '').replace(/\/+$/, '');
|
||||
const treeUrl = trimEnd(url.replace(filepath, ''), '/');
|
||||
|
||||
const tree = await this.readTree(treeUrl, {
|
||||
etag: options?.etag,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { readCspOptions } from './config';
|
||||
import { readCorsOptions, readCspOptions } from './config';
|
||||
|
||||
describe('config', () => {
|
||||
describe('readCspOptions', () => {
|
||||
@@ -42,4 +42,67 @@ describe('config', () => {
|
||||
expect(() => readCspOptions(config)).toThrow(/wanted string-array/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readCorsOptions', () => {
|
||||
it('reads single string', () => {
|
||||
const mockCallback = jest.fn();
|
||||
const config = new ConfigReader({ cors: { origin: 'https://*.value*' } });
|
||||
const cors = readCorsOptions(config);
|
||||
expect(cors).toEqual(
|
||||
expect.objectContaining({
|
||||
origin: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
const origin = cors?.origin as Function;
|
||||
origin('https://a.value', mockCallback); // valid origin
|
||||
origin('http://a.value', mockCallback); // invalid origin
|
||||
origin(undefined, mockCallback); // when not origin needs to reject the call
|
||||
|
||||
expect(mockCallback.mock.calls[0][0]).toBe(null);
|
||||
expect(mockCallback.mock.calls[1][0]).toBe(null);
|
||||
|
||||
expect(mockCallback.mock.calls[0][1]).toBe(true);
|
||||
expect(mockCallback.mock.calls[1][1]).toBe(false);
|
||||
expect(mockCallback.mock.calls[2][1]).toBe(false);
|
||||
});
|
||||
|
||||
it('reads string array', () => {
|
||||
const mockCallback = jest.fn();
|
||||
const config = new ConfigReader({
|
||||
cors: {
|
||||
origin: ['http?(s)://*.value?(-+([0-9])).com', 'http://*.value'],
|
||||
},
|
||||
});
|
||||
const cors = readCorsOptions(config);
|
||||
expect(cors).toEqual(
|
||||
expect.objectContaining({
|
||||
origin: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
const origin = cors?.origin as Function;
|
||||
origin('https://a.b.c.value-9.com', mockCallback);
|
||||
origin('http://a.value-999.com', mockCallback);
|
||||
origin('http://a.value', mockCallback);
|
||||
origin('http://a.valuex', mockCallback);
|
||||
|
||||
expect(mockCallback.mock.calls[0][0]).toBe(null);
|
||||
expect(mockCallback.mock.calls[1][0]).toBe(null);
|
||||
expect(mockCallback.mock.calls[2][0]).toBe(null);
|
||||
expect(mockCallback.mock.calls[3][0]).toBe(null);
|
||||
|
||||
expect(mockCallback.mock.calls[0][1]).toBe(true);
|
||||
expect(mockCallback.mock.calls[1][1]).toBe(true);
|
||||
expect(mockCallback.mock.calls[2][1]).toBe(true);
|
||||
expect(mockCallback.mock.calls[3][1]).toBe(false);
|
||||
});
|
||||
|
||||
it('reads undefined origin', () => {
|
||||
const config = new ConfigReader({
|
||||
cors: {},
|
||||
});
|
||||
const cors = readCorsOptions(config);
|
||||
expect(cors).toEqual(expect.objectContaining({}));
|
||||
expect(cors?.origin).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { CorsOptions } from 'cors';
|
||||
import { Minimatch } from 'minimatch';
|
||||
|
||||
export type BaseOptions = {
|
||||
listenPort?: string | number;
|
||||
@@ -46,6 +47,13 @@ export type CertificateAttributes = {
|
||||
*/
|
||||
export type CspOptions = Record<string, string[]>;
|
||||
|
||||
type StaticOrigin = boolean | string | RegExp | (boolean | string | RegExp)[];
|
||||
|
||||
type CustomOrigin = (
|
||||
requestOrigin: string | undefined,
|
||||
callback: (err: Error | null, origin?: StaticOrigin) => void,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Reads some base options out of a config object.
|
||||
*
|
||||
@@ -78,7 +86,7 @@ export function readBaseOptions(config: Config): BaseOptions {
|
||||
typeof port !== 'string'
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid type in config for key 'backend.listen.post', got ${typeof port}, wanted string or number`,
|
||||
`Invalid type in config for key 'backend.listen.port', got ${typeof port}, wanted string or number`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,7 +120,7 @@ export function readCorsOptions(config: Config): CorsOptions | undefined {
|
||||
}
|
||||
|
||||
return removeUnknown({
|
||||
origin: getOptionalStringOrStrings(cc, 'origin'),
|
||||
origin: createCorsOriginMatcher(getOptionalStringOrStrings(cc, 'origin')),
|
||||
methods: getOptionalStringOrStrings(cc, 'methods'),
|
||||
allowedHeaders: getOptionalStringOrStrings(cc, 'allowedHeaders'),
|
||||
exposedHeaders: getOptionalStringOrStrings(cc, 'exposedHeaders'),
|
||||
@@ -207,16 +215,45 @@ function getOptionalStringOrStrings(
|
||||
key: string,
|
||||
): string | string[] | undefined {
|
||||
const value = config.getOptional(key);
|
||||
if (
|
||||
value === undefined ||
|
||||
typeof value === 'string' ||
|
||||
isStringArray(value)
|
||||
) {
|
||||
if (value === undefined || isStringOrStrings(value)) {
|
||||
return value;
|
||||
}
|
||||
throw new Error(`Expected string or array of strings, got ${typeof value}`);
|
||||
}
|
||||
|
||||
function createCorsOriginMatcher(
|
||||
originValue: string | string[] | undefined,
|
||||
): CustomOrigin | undefined {
|
||||
if (originValue === undefined) {
|
||||
return originValue;
|
||||
}
|
||||
|
||||
if (!isStringOrStrings(originValue)) {
|
||||
throw new Error(
|
||||
`Expected string or array of strings, got ${typeof originValue}`,
|
||||
);
|
||||
}
|
||||
|
||||
const allowedOrigin =
|
||||
typeof originValue === 'string' ? [originValue] : originValue;
|
||||
|
||||
const allowedOriginPatterns =
|
||||
allowedOrigin?.map(
|
||||
pattern => new Minimatch(pattern, { nocase: true, noglobstar: true }),
|
||||
) ?? [];
|
||||
|
||||
return (origin, callback) => {
|
||||
return callback(
|
||||
null,
|
||||
allowedOriginPatterns.some(pattern => pattern.match(origin ?? '')),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function isStringOrStrings(value: any): value is string | string[] {
|
||||
return typeof value === 'string' || isStringArray(value);
|
||||
}
|
||||
|
||||
function isStringArray(value: any): value is string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# example-backend
|
||||
|
||||
## 0.2.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-backend@0.16.0
|
||||
- @backstage/catalog-model@0.9.4
|
||||
- @backstage/plugin-proxy-backend@0.2.13
|
||||
- @backstage/plugin-auth-backend@0.4.3
|
||||
- @backstage/backend-common@0.9.6
|
||||
- @backstage/catalog-client@0.5.0
|
||||
- @backstage/integration@0.6.7
|
||||
- @backstage/plugin-scaffolder-backend@0.15.7
|
||||
- example-app@0.2.49
|
||||
- @backstage/plugin-badges-backend@0.1.11
|
||||
- @backstage/plugin-code-coverage-backend@0.1.12
|
||||
- @backstage/plugin-jenkins-backend@0.1.6
|
||||
- @backstage/plugin-techdocs-backend@0.10.4
|
||||
- @backstage/plugin-todo-backend@0.1.13
|
||||
|
||||
## 0.2.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.9.5
|
||||
- @backstage/plugin-catalog-backend@0.15.0
|
||||
- @backstage/plugin-azure-devops-backend@0.1.1
|
||||
- @backstage/integration@0.6.6
|
||||
- @backstage/plugin-auth-backend@0.4.2
|
||||
- example-app@0.2.48
|
||||
|
||||
## 0.2.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend",
|
||||
"version": "0.2.47",
|
||||
"version": "0.2.49",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -24,35 +24,36 @@
|
||||
"migrate:create": "knex migrate:make -x ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.9.4",
|
||||
"@backstage/catalog-client": "^0.4.0",
|
||||
"@backstage/catalog-model": "^0.9.3",
|
||||
"@backstage/backend-common": "^0.9.6",
|
||||
"@backstage/catalog-client": "^0.5.0",
|
||||
"@backstage/catalog-model": "^0.9.4",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/integration": "^0.6.5",
|
||||
"@backstage/integration": "^0.6.7",
|
||||
"@backstage/plugin-app-backend": "^0.3.16",
|
||||
"@backstage/plugin-auth-backend": "^0.4.1",
|
||||
"@backstage/plugin-badges-backend": "^0.1.10",
|
||||
"@backstage/plugin-catalog-backend": "^0.14.0",
|
||||
"@backstage/plugin-code-coverage-backend": "^0.1.11",
|
||||
"@backstage/plugin-auth-backend": "^0.4.3",
|
||||
"@backstage/plugin-azure-devops-backend": "^0.1.1",
|
||||
"@backstage/plugin-badges-backend": "^0.1.11",
|
||||
"@backstage/plugin-catalog-backend": "^0.16.0",
|
||||
"@backstage/plugin-code-coverage-backend": "^0.1.12",
|
||||
"@backstage/plugin-graphql-backend": "^0.1.9",
|
||||
"@backstage/plugin-jenkins-backend": "^0.1.5",
|
||||
"@backstage/plugin-jenkins-backend": "^0.1.6",
|
||||
"@backstage/plugin-kubernetes-backend": "^0.3.16",
|
||||
"@backstage/plugin-kafka-backend": "^0.2.10",
|
||||
"@backstage/plugin-proxy-backend": "^0.2.12",
|
||||
"@backstage/plugin-proxy-backend": "^0.2.13",
|
||||
"@backstage/plugin-rollbar-backend": "^0.1.15",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.15.6",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.15.7",
|
||||
"@backstage/plugin-scaffolder-backend-module-rails": "^0.1.5",
|
||||
"@backstage/plugin-search-backend": "^0.2.6",
|
||||
"@backstage/plugin-search-backend-node": "^0.4.2",
|
||||
"@backstage/plugin-search-backend-module-elasticsearch": "^0.0.4",
|
||||
"@backstage/plugin-search-backend-module-pg": "^0.2.1",
|
||||
"@backstage/plugin-techdocs-backend": "^0.10.3",
|
||||
"@backstage/plugin-todo-backend": "^0.1.12",
|
||||
"@backstage/plugin-techdocs-backend": "^0.10.4",
|
||||
"@backstage/plugin-todo-backend": "^0.1.13",
|
||||
"@gitbeaker/node": "^30.2.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"dockerode": "^3.2.1",
|
||||
"example-app": "^0.2.47",
|
||||
"example-app": "^0.2.49",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"express-prom-bundle": "^6.3.6",
|
||||
@@ -64,7 +65,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.13",
|
||||
"@backstage/cli": "^0.7.15",
|
||||
"@types/dockerode": "^3.2.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/express-serve-static-core": "^4.17.5"
|
||||
|
||||
@@ -38,6 +38,7 @@ import { Config } from '@backstage/config';
|
||||
import healthcheck from './plugins/healthcheck';
|
||||
import { metricsInit, metricsHandler } from './metrics';
|
||||
import auth from './plugins/auth';
|
||||
import azureDevOps from './plugins/azuredevops';
|
||||
import catalog from './plugins/catalog';
|
||||
import codeCoverage from './plugins/codecoverage';
|
||||
import kubernetes from './plugins/kubernetes';
|
||||
@@ -94,6 +95,7 @@ async function main() {
|
||||
);
|
||||
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
|
||||
const authEnv = useHotMemoize(module, () => createEnv('auth'));
|
||||
const azureDevOpsEnv = useHotMemoize(module, () => createEnv('azure-devops'));
|
||||
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
|
||||
const rollbarEnv = useHotMemoize(module, () => createEnv('rollbar'));
|
||||
const searchEnv = useHotMemoize(module, () => createEnv('search'));
|
||||
@@ -112,6 +114,7 @@ async function main() {
|
||||
apiRouter.use('/rollbar', await rollbar(rollbarEnv));
|
||||
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
|
||||
apiRouter.use('/auth', await auth(authEnv));
|
||||
apiRouter.use('/azure-devops', await azureDevOps(azureDevOpsEnv));
|
||||
apiRouter.use('/search', await search(searchEnv));
|
||||
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
|
||||
apiRouter.use('/todo', await todo(todoEnv));
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createRouter } from '@backstage/plugin-azure-devops-backend';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
return await createRouter({ logger, config });
|
||||
}
|
||||
@@ -1,5 +1,22 @@
|
||||
# @backstage/catalog-client
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- bb0f6b8a0f: Updates the `<EntitySwitch if={asyncMethod}/>` to accept asynchronous `if` functions.
|
||||
|
||||
Adds the new `getEntityAncestors` method to `CatalogClient`.
|
||||
|
||||
Updates the `<EntityProcessingErrorsPanel />` to make use of the ancestry endpoint to display errors for entities further up the ancestry tree. This makes it easier to discover issues where for example the origin location has been removed or malformed.
|
||||
|
||||
`hasCatalogProcessingErrors()` is now changed to be asynchronous so any calls outside the already established entitySwitch need to be awaited.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/catalog-model@0.9.4
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -38,6 +38,11 @@ export interface CatalogApi {
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogListResponse<Entity>>;
|
||||
// (undocumented)
|
||||
getEntityAncestors(
|
||||
request: CatalogEntityAncestorsRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogEntityAncestorsResponse>;
|
||||
// (undocumented)
|
||||
getEntityByName(
|
||||
name: EntityName,
|
||||
options?: CatalogRequestOptions,
|
||||
@@ -88,6 +93,11 @@ export class CatalogClient implements CatalogApi {
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogListResponse<Entity>>;
|
||||
// (undocumented)
|
||||
getEntityAncestors(
|
||||
request: CatalogEntityAncestorsRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogEntityAncestorsResponse>;
|
||||
// (undocumented)
|
||||
getEntityByName(
|
||||
compoundName: EntityName,
|
||||
options?: CatalogRequestOptions,
|
||||
@@ -133,6 +143,20 @@ export type CatalogEntitiesRequest = {
|
||||
fields?: string[] | undefined;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CatalogEntityAncestorsRequest = {
|
||||
entityRef: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CatalogEntityAncestorsResponse = {
|
||||
root: EntityName;
|
||||
items: {
|
||||
entity: Entity;
|
||||
parents: EntityName[];
|
||||
}[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CatalogListResponse<T> = {
|
||||
items: T[];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/catalog-client",
|
||||
"description": "An isomorphic client for the catalog backend",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,13 +30,12 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.3",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/catalog-model": "^0.9.4",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"cross-fetch": "^3.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.13",
|
||||
"@backstage/cli": "^0.7.15",
|
||||
"@types/jest": "^26.0.7",
|
||||
"msw": "^0.29.0"
|
||||
},
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
Location,
|
||||
LOCATION_ANNOTATION,
|
||||
ORIGIN_LOCATION_ANNOTATION,
|
||||
parseEntityRef,
|
||||
stringifyEntityRef,
|
||||
stringifyLocationReference,
|
||||
} from '@backstage/catalog-model';
|
||||
@@ -33,6 +34,8 @@ import {
|
||||
CatalogEntitiesRequest,
|
||||
CatalogListResponse,
|
||||
CatalogRequestOptions,
|
||||
CatalogEntityAncestorsRequest,
|
||||
CatalogEntityAncestorsResponse,
|
||||
} from './types/api';
|
||||
import { DiscoveryApi } from './types/discovery';
|
||||
|
||||
@@ -44,6 +47,20 @@ export class CatalogClient implements CatalogApi {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
}
|
||||
|
||||
async getEntityAncestors(
|
||||
request: CatalogEntityAncestorsRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogEntityAncestorsResponse> {
|
||||
const { kind, namespace, name } = parseEntityRef(request.entityRef);
|
||||
return await this.requestRequired(
|
||||
'GET',
|
||||
`/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent(
|
||||
namespace,
|
||||
)}/${encodeURIComponent(name)}/ancestry`,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async getLocationById(
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
|
||||
@@ -28,6 +28,17 @@ export type CatalogEntitiesRequest = {
|
||||
fields?: string[] | undefined;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type CatalogEntityAncestorsRequest = {
|
||||
entityRef: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type CatalogEntityAncestorsResponse = {
|
||||
root: EntityName;
|
||||
items: { entity: Entity; parents: EntityName[] }[];
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type CatalogListResponse<T> = {
|
||||
items: T[];
|
||||
@@ -45,6 +56,10 @@ export interface CatalogApi {
|
||||
request?: CatalogEntitiesRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogListResponse<Entity>>;
|
||||
getEntityAncestors(
|
||||
request: CatalogEntityAncestorsRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogEntityAncestorsResponse>;
|
||||
getEntityByName(
|
||||
name: EntityName,
|
||||
options?: CatalogRequestOptions,
|
||||
|
||||
@@ -21,6 +21,8 @@ export type {
|
||||
CatalogEntitiesRequest,
|
||||
CatalogListResponse,
|
||||
CatalogRequestOptions,
|
||||
CatalogEntityAncestorsRequest,
|
||||
CatalogEntityAncestorsResponse,
|
||||
} from './api';
|
||||
export type { DiscoveryApi } from './discovery';
|
||||
export { CATALOG_FILTER_EXISTS } from './api';
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/catalog-model
|
||||
|
||||
## 0.9.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 957e4b3351: Updated dependencies
|
||||
- ca0559444c: Avoid usage of `.to*Case()`, preferring `.toLocale*Case('en-US')` instead.
|
||||
|
||||
## 0.9.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -10,12 +10,9 @@ import { SerializedError } from '@backstage/errors';
|
||||
import * as yup from 'yup';
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const analyzeLocationSchema: yup.ObjectSchema<
|
||||
{
|
||||
location: LocationSpec;
|
||||
},
|
||||
object
|
||||
>;
|
||||
export const analyzeLocationSchema: yup.SchemaOf<{
|
||||
location: LocationSpec;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
interface ApiEntityV1alpha1 extends Entity {
|
||||
@@ -337,7 +334,7 @@ export { LocationEntityV1alpha1 };
|
||||
export const locationEntityV1alpha1Validator: KindValidator;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const locationSchema: yup.ObjectSchema<Location_2, object>;
|
||||
export const locationSchema: yup.SchemaOf<Location_2>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type LocationSpec = {
|
||||
@@ -347,7 +344,7 @@ export type LocationSpec = {
|
||||
};
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const locationSpecSchema: yup.ObjectSchema<LocationSpec, object>;
|
||||
export const locationSpecSchema: yup.SchemaOf<LocationSpec>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function makeValidator(overrides?: Partial<Validators>): Validators;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"description": "Types and validators that help describe the model of a Backstage Catalog",
|
||||
"version": "0.9.3",
|
||||
"version": "0.9.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -33,16 +33,15 @@
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/errors": "^0.1.2",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"@types/yup": "^0.29.8",
|
||||
"@types/yup": "^0.29.13",
|
||||
"ajv": "^7.0.3",
|
||||
"json-schema": "^0.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"uuid": "^8.0.0",
|
||||
"yup": "^0.29.3"
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.13",
|
||||
"@types/express": "^4.17.6",
|
||||
"@backstage/cli": "^0.7.15",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"yaml": "^1.9.2"
|
||||
|
||||
@@ -248,7 +248,9 @@ export function stringifyEntityRef(
|
||||
name = ref.name;
|
||||
}
|
||||
|
||||
return `${kind.toLowerCase()}:${namespace.toLowerCase()}/${name.toLowerCase()}`;
|
||||
return `${kind.toLocaleLowerCase('en-US')}:${namespace.toLocaleLowerCase(
|
||||
'en-US',
|
||||
)}/${name.toLocaleLowerCase('en-US')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,8 +298,10 @@ export function compareEntityToRef(
|
||||
}
|
||||
|
||||
return (
|
||||
entityKind.toLowerCase() === refKind.toLowerCase() &&
|
||||
entityNamespace.toLowerCase() === refNamespace.toLowerCase() &&
|
||||
entityName.toLowerCase() === refName.toLowerCase()
|
||||
entityKind.toLocaleLowerCase('en-US') ===
|
||||
refKind.toLocaleLowerCase('en-US') &&
|
||||
entityNamespace.toLocaleLowerCase('en-US') ===
|
||||
refNamespace.toLocaleLowerCase('en-US') &&
|
||||
entityName.toLocaleLowerCase('en-US') === refName.toLocaleLowerCase('en-US')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ import { LocationSpec, Location } from './types';
|
||||
* @public
|
||||
* @deprecated Use JSONSchema or validators instead.
|
||||
*/
|
||||
export const locationSpecSchema = yup
|
||||
.object<LocationSpec>({
|
||||
export const locationSpecSchema: yup.SchemaOf<LocationSpec> = yup
|
||||
.object({
|
||||
type: yup.string().required(),
|
||||
target: yup.string().required(),
|
||||
presence: yup.string(),
|
||||
presence: yup.mixed().oneOf(['required', 'optional']),
|
||||
})
|
||||
.noUnknown()
|
||||
.required();
|
||||
@@ -34,11 +34,12 @@ export const locationSpecSchema = yup
|
||||
* @public
|
||||
* @deprecated Use JSONSchema or validators instead.
|
||||
*/
|
||||
export const locationSchema = yup
|
||||
.object<Location>({
|
||||
export const locationSchema: yup.SchemaOf<Location> = yup
|
||||
.object({
|
||||
id: yup.string().required(),
|
||||
type: yup.string().required(),
|
||||
target: yup.string().required(),
|
||||
presence: yup.mixed().oneOf(['required', 'optional']),
|
||||
})
|
||||
.noUnknown()
|
||||
.required();
|
||||
@@ -47,9 +48,10 @@ export const locationSchema = yup
|
||||
* @public
|
||||
* @deprecated Use JSONSchema or validators instead.
|
||||
*/
|
||||
export const analyzeLocationSchema = yup
|
||||
.object<{ location: LocationSpec }>({
|
||||
location: locationSpecSchema,
|
||||
})
|
||||
.noUnknown()
|
||||
.required();
|
||||
export const analyzeLocationSchema: yup.SchemaOf<{ location: LocationSpec }> =
|
||||
yup
|
||||
.object({
|
||||
location: locationSpecSchema,
|
||||
})
|
||||
.noUnknown()
|
||||
.required();
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/cli-common
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ca0559444c: Avoid usage of `.to*Case()`, preferring `.toLocale*Case('en-US')` instead.
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli-common",
|
||||
"description": "Common functionality used by cli, backend, and create-app",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"private": false,
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -115,7 +115,7 @@ export function findPaths(searchDir: string): Paths {
|
||||
// Drive letter can end up being lowercased here on Windows, bring back to uppercase for consistency
|
||||
const targetDir = fs
|
||||
.realpathSync(process.cwd())
|
||||
.replace(/^[a-z]:/, str => str.toUpperCase());
|
||||
.replace(/^[a-z]:/, str => str.toLocaleUpperCase('en-US'));
|
||||
|
||||
// Lazy load this as it will throw an error if we're not inside the Backstage repo.
|
||||
let ownRoot = '';
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.7.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ae4680b88d: The `create-plugin` command now passes the extension name via the `name` key
|
||||
in `createRoutableExtension()` calls in newly created plugins.
|
||||
- df1242ffe4: Adding `--inspect-brk` as an option when debugging backend for development
|
||||
- c7f2a2307d: When creating a backend plugin with `--backend` flag, don't add `-backend` if it's already suffixed
|
||||
- 185fec5c0c: The default jest configuration used by the `test` command now supports yarn workspaces. By running `backstage-cli test` in the root of a monorepo, all packages will now automatically be included in the test suite and it will run just like it does within a package. Each package in the monorepo will still use its own local jest configuration, and only packages that have `backstage-cli test` in the `test` script within `package.json` will be included.
|
||||
- Updated dependencies
|
||||
- @backstage/config-loader@0.6.10
|
||||
- @backstage/cli-common@0.1.4
|
||||
|
||||
## 0.7.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3a8704f16b: Only serve static assets if there is a public folder during `app:serve` and `plugin:serve`. This fixes a common bug that would break `plugin:serve` with an `EBUSY` error.
|
||||
- 40199b61d6: Configuration schema is now also collected from the root `package.json` if it exists.
|
||||
- 2a6c393c06: The `create-plugin` command now prefers dependency versions ranges that are already in the lockfile.
|
||||
- 58f91943ab: Improved ´plugin:diff´ check for the `package.json` `"files"` field.
|
||||
- 12e074a6e4: Fix duplication checks to stop looking for the old core packages, and to allow some explicitly
|
||||
- Updated dependencies
|
||||
- @backstage/config-loader@0.6.9
|
||||
|
||||
## 0.7.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+63
-10
@@ -16,20 +16,23 @@
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const glob = require('util').promisify(require('glob'));
|
||||
|
||||
async function getConfig() {
|
||||
async function getProjectConfig(targetPath) {
|
||||
const configJsPath = path.resolve(targetPath, 'jest.config.js');
|
||||
const configTsPath = path.resolve(targetPath, 'jest.config.ts');
|
||||
// If the package has it's own jest config, we use that instead.
|
||||
if (await fs.pathExists('jest.config.js')) {
|
||||
return require(path.resolve('jest.config.js'));
|
||||
} else if (await fs.pathExists('jest.config.ts')) {
|
||||
return require(path.resolve('jest.config.ts'));
|
||||
if (await fs.pathExists(configJsPath)) {
|
||||
return require(configJsPath);
|
||||
} else if (await fs.pathExists(configTsPath)) {
|
||||
return require(configTsPath);
|
||||
}
|
||||
|
||||
// We read all "jest" config fields in package.json files all the way to the filesystem root.
|
||||
// All configs are merged together to create the final config, with longer paths taking precedence.
|
||||
// The merging of the configs is shallow, meaning e.g. all transforms are replaced if new ones are defined.
|
||||
const pkgJsonConfigs = [];
|
||||
let currentPath = process.cwd();
|
||||
let currentPath = targetPath;
|
||||
|
||||
// Some sanity check to avoid infinite loop
|
||||
for (let i = 0; i < 100; i++) {
|
||||
@@ -70,8 +73,8 @@ async function getConfig() {
|
||||
const transformModulePattern = transformModules && `(?!${transformModules})`;
|
||||
|
||||
const options = {
|
||||
rootDir: path.resolve('src'),
|
||||
coverageDirectory: path.resolve('coverage'),
|
||||
rootDir: path.resolve(targetPath, 'src'),
|
||||
coverageDirectory: path.resolve(targetPath, 'coverage'),
|
||||
collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/*.d.ts'],
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less|scss|sss|styl)$': require.resolve('jest-css-modules'),
|
||||
@@ -96,11 +99,61 @@ async function getConfig() {
|
||||
};
|
||||
|
||||
// Use src/setupTests.ts as the default location for configuring test env
|
||||
if (fs.existsSync('src/setupTests.ts')) {
|
||||
if (fs.existsSync(path.resolve(targetPath, 'src/setupTests.ts'))) {
|
||||
options.setupFilesAfterEnv = ['<rootDir>/setupTests.ts'];
|
||||
}
|
||||
|
||||
return Object.assign(options, ...pkgJsonConfigs);
|
||||
}
|
||||
|
||||
module.exports = getConfig();
|
||||
// This loads the root jest config, which in turn will either refer to a single
|
||||
// configuration for the current package, or a collection of configurations for
|
||||
// the target workspace packages
|
||||
async function getRootConfig() {
|
||||
const targetPath = process.cwd();
|
||||
const targetPackagePath = path.resolve(targetPath, 'package.json');
|
||||
const exists = await fs.pathExists(targetPackagePath);
|
||||
|
||||
if (!exists) {
|
||||
return getProjectConfig(targetPath);
|
||||
}
|
||||
|
||||
// Check whether the current package is a workspace root or not
|
||||
const data = await fs.readJson(targetPackagePath);
|
||||
const workspacePatterns = data.workspaces && data.workspaces.packages;
|
||||
if (!workspacePatterns) {
|
||||
return getProjectConfig(targetPath);
|
||||
}
|
||||
|
||||
// If the target package is a workspace root, we find all packages in the
|
||||
// workspace and load those in as separate jest projects instead.
|
||||
const projectPaths = await Promise.all(
|
||||
workspacePatterns.map(pattern => glob(path.join(targetPath, pattern))),
|
||||
).then(_ => _.flat());
|
||||
|
||||
const configs = await Promise.all(
|
||||
projectPaths.flat().map(async projectPath => {
|
||||
const packagePath = path.resolve(projectPath, 'package.json');
|
||||
if (!(await fs.pathExists(packagePath))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We check for the presence of "backstage-cli test" in the package test
|
||||
// script to determine whether a given package should be tested
|
||||
const packageData = await fs.readJson(packagePath);
|
||||
const testScript = packageData.scripts && packageData.scripts.test;
|
||||
if (testScript && testScript.includes('backstage-cli test')) {
|
||||
return await getProjectConfig(projectPath);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}),
|
||||
).then(cs => cs.filter(Boolean));
|
||||
|
||||
return {
|
||||
rootDir: targetPath,
|
||||
projects: configs,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = getRootConfig();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.7.13",
|
||||
"version": "0.7.15",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -30,9 +30,9 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.4.4",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.4.4",
|
||||
"@backstage/cli-common": "^0.1.3",
|
||||
"@backstage/cli-common": "^0.1.4",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/config-loader": "^0.6.8",
|
||||
"@backstage/config-loader": "^0.6.10",
|
||||
"@hot-loader/react-dom": "^16.13.0",
|
||||
"@lerna/package-graph": "^4.0.0",
|
||||
"@lerna/project": "^4.0.0",
|
||||
@@ -76,6 +76,7 @@
|
||||
"express": "^4.17.1",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.5",
|
||||
"fs-extra": "9.1.0",
|
||||
"glob": "^7.1.7",
|
||||
"handlebars": "^4.7.3",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"inquirer": "^7.0.4",
|
||||
@@ -117,13 +118,13 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.9.4",
|
||||
"@backstage/backend-common": "^0.9.6",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/core-components": "^0.5.0",
|
||||
"@backstage/core-plugin-api": "^0.1.8",
|
||||
"@backstage/core-app-api": "^0.1.14",
|
||||
"@backstage/dev-utils": "^0.2.10",
|
||||
"@backstage/test-utils": "^0.1.17",
|
||||
"@backstage/core-components": "^0.6.1",
|
||||
"@backstage/core-plugin-api": "^0.1.10",
|
||||
"@backstage/core-app-api": "^0.1.16",
|
||||
"@backstage/dev-utils": "^0.2.11",
|
||||
"@backstage/test-utils": "^0.1.18",
|
||||
"@backstage/theme": "^0.2.10",
|
||||
"@types/diff": "^5.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user