Merge remote-tracking branch 'origin/master' into orkohunter/techdocs-publish-to-cloud-storage

This commit is contained in:
Himanshu Mishra
2020-12-03 22:53:45 +01:00
200 changed files with 4736 additions and 974 deletions
-13
View File
@@ -1,13 +0,0 @@
---
'@backstage/create-app': patch
---
Optimized the `yarn install` step in the backend `Dockerfile`.
To apply these changes to an existing app, make the following changes to `packages/backend/Dockerfile`:
Replace the `RUN yarn install ...` line with the following:
```bash
RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)"
```
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-api-docs': patch
---
Update swagger-ui-react to 3.37.2
@@ -1,5 +0,0 @@
---
'@backstage/plugin-cost-insights': patch
---
Add breakdown view to the Cost Overview panel
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-techdocs-backend': patch
---
Update URL auth format for Gitlab clone
+8
View File
@@ -0,0 +1,8 @@
---
'@backstage/plugin-circleci': patch
'@backstage/plugin-jenkins': patch
---
Refactor to support ADR004 module exporting.
For more information, see https://backstage.io/docs/architecture-decisions/adrs-adr004.
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/plugin-sentry': patch
'@backstage/plugin-welcome': patch
---
Refactor route registration to remove deprecating code
-9
View File
@@ -1,9 +0,0 @@
---
'@backstage/config-loader': minor
---
Fix typo of "visibility" in config schema reference
If you have defined a config element named `visiblity`, you
will need to fix the spelling to `visibility`. For more info,
see https://backstage.io/docs/conf/defining#visibility.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/backend-common': patch
---
Allow the `backend.listen.port` config to be both a number or a string.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Bump versions of `esbuild` and `rollup-plugin-esbuild`
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/plugin-proxy-backend': patch
---
Filter the headers that are sent from the proxied-targed back to the frontend to not forwarded unwanted authentication or
monitoring contexts from other origins (like `Set-Cookie` with e.g. a google analytics context). The implementation reuses
the `allowedHeaders` configuration that now controls both directions `frontend->target` and `target->frontend`.
-13
View File
@@ -1,13 +0,0 @@
---
'@backstage/create-app': patch
---
Removed `"resolutions"` entry for `esbuild` in the root `package.json` in order to use the version specified by `@backstage/cli`.
To apply this change to an existing app, remove the following from your root `package.json`:
```json
"resolutions": {
"esbuild": "0.6.3"
},
```
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/create-app': patch
---
Add [API docs plugin](https://github.com/backstage/backstage/tree/master/plugins/api-docs) to new apps being created through the CLI.
-28
View File
@@ -1,28 +0,0 @@
---
'@backstage/plugin-catalog-backend': minor
'@backstage/plugin-catalog-import': minor
'@backstage/catalog-model': patch
'@backstage/plugin-scaffolder': patch
---
Add Analyze location endpoint to catalog backend. Add catalog-import plugin and replace import-component with it. To start using Analyze location endpoint, you have add it to the `createRouter` function options in the `\backstage\packages\backend\src\plugins\catalog.ts` file:
```ts
export default async function createPlugin(env: PluginEnvironment) {
const builder = new CatalogBuilder(env);
const {
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
locationAnalyzer, //<--
} = await builder.build();
return await createRouter({
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
locationAnalyzer, //<--
logger: env.logger,
});
}
```
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/plugin-catalog-backend': patch
---
Gracefully handle missing codeowners.
The CodeOwnersProcessor now also takes a logger as a parameter.
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/cli': minor
'@backstage/plugin-cost-insights': patch
---
sort product panels and navigation menu by greatest cost
update tsconfig.json to use ES2020 api
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-techdocs': patch
---
Use type EntityName from catalog-model for entities
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog': patch
---
Use the OWNED_BY relation and compare it to the users MEMBER_OF relation. The user entity is searched by name, based on the userId of the identity.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-proxy-backend': patch
---
Add configuration schema for the commonly used properties
+3
View File
@@ -294,6 +294,9 @@ costInsights:
bigQuery:
name: BigQuery
icon: search
events:
name: Events
icon: data
metrics:
DAU:
name: Daily Active Users
+1 -1
View File
@@ -93,6 +93,6 @@ sign-in methods.
More details are provided in dedicated sections of the documentation.
- [OAuth](./oauth.md): Description of the generic OAuth flow implemented by the
[auth-backend](../../plugins/auth-backend).
[auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend).
- [Glossary](./glossary.md): Glossary of some common terms related to the auth
flows.
@@ -31,6 +31,7 @@ we recommend that you name them `catalog-info.yaml`.
- [Kind: Resource](#kind-resource)
- [Kind: System](#kind-system)
- [Kind: Domain](#kind-domain)
- [Kind: Location](#kind-location)
## Overall Shape Of An Entity
@@ -884,3 +885,58 @@ This kind is not yet defined, but is reserved [for future use](system-model.md).
## Kind: Domain
This kind is not yet defined, but is reserved [for future use](system-model.md).
## Kind: Location
Describes the following entity kind:
| Field | Value |
| ------------ | ----------------------- |
| `apiVersion` | `backstage.io/v1alpha1` |
| `kind` | `Location` |
A location is a marker that references other places to look for catalog data.
Descriptor files for this kind may look as follows.
```yaml
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: org-data
spec:
type: url
targets:
- http://github.com/myorg/myproject/org-data-dump/catalog-info-staff.yaml
- http://github.com/myorg/myproject/org-data-dump/catalog-info-consultants.yaml
```
In addition to the [common envelope metadata](#common-to-all-kinds-the-metadata)
shape, this kind has the following structure.
### `apiVersion` and `kind` [required]
Exactly equal to `backstage.io/v1alpha1` and `Location`, respectively.
### `spec.type` [optional]
The single location type, that's common to the targets specified in the spec. If
it is left out, it is inherited from the location type that originally read the
entity data. For example, if you have a `url` type location, that when read
results in a `Location` kind entity with no `spec.type`, then the referenced
targets in the entity will implicitly also be of `url` type. This is useful
because you can define a hierarchy of things in a directory structure using
relative target paths (see below), and it will work out no matter if it's
consumed locally on disk from a `file` location, or as uploaded on a VCS.
### `spec.target` [optional]
A single target as a string. Can be either an absolute path/URL (depending on
the type), or a relative path such as `./details/catalog-info.yaml` which is
resolved relative to the location of this Location entity itself.
### `spec.targets` [optional]
A list of targets as strings. They can all be either absolute paths/URLs
(depending on the type), or relative paths such as `./details/catalog-info.yaml`
which are resolved relative to the location of this Location entity itself.
+40
View File
@@ -1,5 +1,45 @@
# example-app
## 0.2.5
### Patch Changes
- Updated dependencies [7eb8bfe4a]
- Updated dependencies [fe7257ff0]
- Updated dependencies [a2cfa311a]
- Updated dependencies [69f38457f]
- Updated dependencies [bec334b33]
- Updated dependencies [303c5ea17]
- Updated dependencies [b4488ddb0]
- Updated dependencies [4a655c89d]
- Updated dependencies [08835a61d]
- Updated dependencies [a9fd599f7]
- Updated dependencies [8a16e8af8]
- Updated dependencies [bcc211a08]
- Updated dependencies [00670a96e]
- Updated dependencies [da2ad65cb]
- Updated dependencies [ebf37bbae]
- @backstage/plugin-api-docs@0.3.1
- @backstage/plugin-cost-insights@0.4.2
- @backstage/plugin-sentry@0.2.4
- @backstage/plugin-welcome@0.2.2
- @backstage/cli@0.4.0
- @backstage/catalog-model@0.4.0
- @backstage/plugin-catalog-import@0.3.0
- @backstage/plugin-scaffolder@0.3.2
- @backstage/plugin-kubernetes@0.3.1
- @backstage/plugin-techdocs@0.3.1
- @backstage/plugin-catalog@0.2.5
- @backstage/test-utils@0.1.4
- @backstage/plugin-circleci@0.2.3
- @backstage/plugin-cloudbuild@0.2.3
- @backstage/plugin-github-actions@0.2.3
- @backstage/plugin-jenkins@0.3.2
- @backstage/plugin-lighthouse@0.2.4
- @backstage/plugin-register-component@0.2.3
- @backstage/plugin-rollbar@0.2.5
- @backstage/plugin-search@0.2.2
## 0.2.4
### Patch Changes
+21 -21
View File
@@ -1,37 +1,37 @@
{
"name": "example-app",
"version": "0.2.4",
"version": "0.2.5",
"private": true,
"bundled": true,
"dependencies": {
"@backstage/catalog-model": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/catalog-model": "^0.4.0",
"@backstage/cli": "^0.4.0",
"@backstage/core": "^0.3.2",
"@backstage/plugin-api-docs": "^0.3.0",
"@backstage/plugin-catalog": "^0.2.4",
"@backstage/plugin-catalog-import": "^0.2.0",
"@backstage/plugin-circleci": "^0.2.2",
"@backstage/plugin-cloudbuild": "^0.2.2",
"@backstage/plugin-cost-insights": "^0.4.1",
"@backstage/plugin-api-docs": "^0.3.1",
"@backstage/plugin-catalog": "^0.2.5",
"@backstage/plugin-catalog-import": "^0.3.0",
"@backstage/plugin-circleci": "^0.2.3",
"@backstage/plugin-cloudbuild": "^0.2.3",
"@backstage/plugin-cost-insights": "^0.4.2",
"@backstage/plugin-explore": "^0.2.1",
"@backstage/plugin-gcp-projects": "^0.2.1",
"@backstage/plugin-github-actions": "^0.2.2",
"@backstage/plugin-github-actions": "^0.2.3",
"@backstage/plugin-gitops-profiles": "^0.2.1",
"@backstage/plugin-graphiql": "^0.2.1",
"@backstage/plugin-jenkins": "^0.3.1",
"@backstage/plugin-kubernetes": "^0.3.0",
"@backstage/plugin-lighthouse": "^0.2.3",
"@backstage/plugin-jenkins": "^0.3.2",
"@backstage/plugin-kubernetes": "^0.3.1",
"@backstage/plugin-lighthouse": "^0.2.4",
"@backstage/plugin-newrelic": "^0.2.1",
"@backstage/plugin-register-component": "^0.2.2",
"@backstage/plugin-rollbar": "^0.2.4",
"@backstage/plugin-scaffolder": "^0.3.1",
"@backstage/plugin-sentry": "^0.2.3",
"@backstage/plugin-search": "^0.2.1",
"@backstage/plugin-register-component": "^0.2.3",
"@backstage/plugin-rollbar": "^0.2.5",
"@backstage/plugin-scaffolder": "^0.3.2",
"@backstage/plugin-sentry": "^0.2.4",
"@backstage/plugin-search": "^0.2.2",
"@backstage/plugin-tech-radar": "^0.3.0",
"@backstage/plugin-techdocs": "^0.3.0",
"@backstage/plugin-techdocs": "^0.3.1",
"@backstage/plugin-user-settings": "^0.2.2",
"@backstage/plugin-welcome": "^0.2.1",
"@backstage/test-utils": "^0.1.3",
"@backstage/plugin-welcome": "^0.2.2",
"@backstage/test-utils": "^0.1.4",
"@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
+9
View File
@@ -1,5 +1,14 @@
# @backstage/backend-common
## 0.3.3
### Patch Changes
- 612368274: Allow the `backend.listen.port` config to be both a number or a string.
- Updated dependencies [4e7091759]
- Updated dependencies [b4488ddb0]
- @backstage/config-loader@0.4.0
## 0.3.2
### Patch Changes
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.3.2",
"version": "0.3.3",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -31,7 +31,7 @@
"dependencies": {
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.1",
"@backstage/config-loader": "^0.3.0",
"@backstage/config-loader": "^0.4.0",
"@backstage/integration": "^0.1.2",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
@@ -67,8 +67,8 @@
}
},
"devDependencies": {
"@backstage/cli": "^0.3.2",
"@backstage/test-utils": "^0.1.3",
"@backstage/cli": "^0.4.0",
"@backstage/test-utils": "^0.1.4",
"@types/archiver": "^3.1.1",
"@types/compression": "^1.7.0",
"@types/concat-stream": "^1.6.0",
@@ -235,6 +235,40 @@ describe('GithubUrlReader', () => {
expect(indexMarkdownFile.toString()).toBe('# Test\n');
});
it('includes the subdomain in the github url', async () => {
worker.resetHandlers();
worker.use(
rest.get(
'https://ghe.github.com/backstage/mock/archive/repo.tar.gz',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/x-gzip'),
ctx.body(repoBuffer),
),
),
);
const processor = new GithubUrlReader(
{
host: 'ghe.github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
);
const response = await processor.readTree(
'https://ghe.github.com/backstage/mock/tree/repo/docs',
);
const files = await response.files();
expect(files.length).toBe(1);
const indexMarkdownFile = await files[0].content();
expect(indexMarkdownFile.toString()).toBe('# Test\n');
});
it('must specify a branch', async () => {
const processor = new GithubUrlReader(
{
@@ -179,7 +179,7 @@ export class GithubUrlReader implements UrlReader {
name: repoName,
ref,
protocol,
source,
resource,
full_name,
filepath,
} = parseGitUri(url);
@@ -194,7 +194,7 @@ export class GithubUrlReader implements UrlReader {
// TODO(Rugvip): use API to fetch URL instead
const response = await fetch(
new URL(
`${protocol}://${source}/${full_name}/archive/${ref}.tar.gz`,
`${protocol}://${resource}/${full_name}/archive/${ref}.tar.gz`,
).toString(),
);
if (!response.ok) {
+24
View File
@@ -1,5 +1,29 @@
# example-backend
## 0.2.5
### Patch Changes
- Updated dependencies [ae95c7ff3]
- Updated dependencies [b4488ddb0]
- Updated dependencies [612368274]
- Updated dependencies [6a6c7c14e]
- Updated dependencies [08835a61d]
- Updated dependencies [a9fd599f7]
- Updated dependencies [e42402b47]
- Updated dependencies [bcc211a08]
- Updated dependencies [3619ea4c4]
- @backstage/plugin-techdocs-backend@0.3.1
- @backstage/plugin-catalog-backend@0.3.0
- @backstage/backend-common@0.3.3
- @backstage/plugin-proxy-backend@0.2.2
- @backstage/catalog-model@0.4.0
- @backstage/plugin-kubernetes-backend@0.2.1
- @backstage/plugin-app-backend@0.3.2
- example-app@0.2.5
- @backstage/plugin-auth-backend@0.2.5
- @backstage/plugin-scaffolder-backend@0.3.3
## 0.2.4
### Patch Changes
+12 -12
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.2.4",
"version": "0.2.5",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"private": true,
@@ -18,24 +18,24 @@
"migrate:create": "knex migrate:make -x ts"
},
"dependencies": {
"@backstage/backend-common": "^0.3.2",
"@backstage/catalog-model": "^0.3.1",
"@backstage/backend-common": "^0.3.3",
"@backstage/catalog-model": "^0.4.0",
"@backstage/config": "^0.1.1",
"@backstage/plugin-app-backend": "^0.3.1",
"@backstage/plugin-auth-backend": "^0.2.4",
"@backstage/plugin-catalog-backend": "^0.2.3",
"@backstage/plugin-app-backend": "^0.3.2",
"@backstage/plugin-auth-backend": "^0.2.5",
"@backstage/plugin-catalog-backend": "^0.3.0",
"@backstage/plugin-graphql-backend": "^0.1.3",
"@backstage/plugin-kubernetes-backend": "^0.2.0",
"@backstage/plugin-proxy-backend": "^0.2.1",
"@backstage/plugin-kubernetes-backend": "^0.2.1",
"@backstage/plugin-proxy-backend": "^0.2.2",
"@backstage/plugin-rollbar-backend": "^0.1.4",
"@backstage/plugin-scaffolder-backend": "^0.3.2",
"@backstage/plugin-scaffolder-backend": "^0.3.3",
"@backstage/plugin-sentry-backend": "^0.1.3",
"@backstage/plugin-techdocs-backend": "^0.3.0",
"@backstage/plugin-techdocs-backend": "^0.3.1",
"@gitbeaker/node": "^25.2.0",
"@octokit/rest": "^18.0.0",
"azure-devops-node-api": "^10.1.1",
"dockerode": "^3.2.0",
"example-app": "^0.2.4",
"example-app": "^0.2.5",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"knex": "^0.21.6",
@@ -45,7 +45,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.3.2",
"@backstage/cli": "^0.4.0",
"@types/dockerode": "^2.5.32",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
+9
View File
@@ -1,5 +1,14 @@
# @backstage/catalog-client
## 0.3.2
### Patch Changes
- Updated dependencies [08835a61d]
- Updated dependencies [a9fd599f7]
- Updated dependencies [bcc211a08]
- @backstage/catalog-model@0.4.0
## 0.3.1
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-client",
"version": "0.3.1",
"version": "0.3.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,12 +20,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.3.0",
"@backstage/catalog-model": "^0.4.0",
"@backstage/config": "^0.1.1",
"cross-fetch": "^3.0.6"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.4.0",
"@types/jest": "^26.0.7",
"msw": "^0.21.2"
},
+31
View File
@@ -1,5 +1,36 @@
# @backstage/catalog-model
## 0.4.0
### Minor Changes
- bcc211a08: k8s-plugin: refactor approach to use annotation based label-selector
### Patch Changes
- 08835a61d: Add support for relative targets and implicit types in Location entities.
- a9fd599f7: Add Analyze location endpoint to catalog backend. Add catalog-import plugin and replace import-component with it. To start using Analyze location endpoint, you have add it to the `createRouter` function options in the `\backstage\packages\backend\src\plugins\catalog.ts` file:
```ts
export default async function createPlugin(env: PluginEnvironment) {
const builder = new CatalogBuilder(env);
const {
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
locationAnalyzer, //<--
} = await builder.build();
return await createRouter({
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
locationAnalyzer, //<--
logger: env.logger,
});
}
```
## 0.3.1
### Patch Changes
@@ -0,0 +1,10 @@
# Examples with relative paths
These examples use Location entities with relative paths
[see #3513](https://github.com/backstage/backstage/pull/3513).
The `examples` sibling directory will be replaced with these contents no sooner
than December 16 2020, to give consumers time to adopt the above change.
The contents of this directory is here for consumers who are on the edge and
want to use the new facility before December 16.
@@ -0,0 +1,8 @@
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: acme-corp
description: A collection of all Backstage example Groups
spec:
targets:
- ./acme/org.yaml
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: backstage
description: The backstage sub-department
spec:
type: sub-department
parent: infrastructure
ancestors: [infrastructure, acme-corp]
children: [team-a, team-b]
descendants: [team-a, team-b]
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: boxoffice
description: The boxoffice sub-department
spec:
type: sub-department
parent: infrastructure
ancestors: [infrastructure, acme-corp]
children: [team-c, team-d]
descendants: [team-c, team-d]
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: infrastructure
description: The infra department
spec:
type: department
parent: acme-corp
ancestors: [acme-corp]
children: [backstage, boxoffice]
descendants: [backstage, boxoffice, team-a, team-b, team-c, team-d]
@@ -0,0 +1,26 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: acme-corp
description: The acme-corp organization
spec:
type: organization
ancestors: []
children: [infrastructure]
descendants:
[infrastructure, backstage, boxoffice, team-a, team-b, team-c, team-d]
---
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: example-groups
description: A collection of all Backstage example Groups
spec:
targets:
- ./infrastructure-group.yaml
- ./boxoffice-group.yaml
- ./backstage-group.yaml
- ./team-a-group.yaml
- ./team-b-group.yaml
- ./team-c-group.yaml
- ./team-d-group.yaml
@@ -0,0 +1,58 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: team-a
description: Team A
spec:
type: team
parent: backstage
ancestors: [backstage, infrastructure, acme-corp]
children: []
descendants: []
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: breanna.davison
spec:
profile:
displayName: Breanna Davison
email: breanna-davison@example.com
picture: https://example.com/staff/breanna.jpeg
memberOf: [team-a]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: janelle.dawe
spec:
profile:
displayName: Janelle Dawe
email: janelle-dawe@example.com
picture: https://example.com/staff/janelle.jpeg
memberOf: [team-a]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: nigel.manning
spec:
profile:
displayName: Nigel Manning
email: nigel-manning@example.com
picture: https://example.com/staff/nigel.jpeg
memberOf: [team-a]
---
# This user is added as an example, to make it more easy for the "Guest"
# sign-in option to demonstrate some entities being owned. In a regular org,
# a guest user would probably not be registered like this.
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: guest
spec:
profile:
displayName: Guest User
email: guest@example.com
picture: https://example.com/staff/the-ceos-dog.jpeg
memberOf: [team-a]
@@ -0,0 +1,66 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: team-b
description: Team B
spec:
type: team
parent: backstage
ancestors: [backstage, infrastructure, acme-corp]
children: []
descendants: []
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: amelia.park
spec:
profile:
displayName: Amelia Park
email: amelia-park@example.com
picture: https://example.com/staff/amelia.jpeg
memberOf: [team-b]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: colette.brock
spec:
profile:
displayName: Colette Brock
email: colette-brock@example.com
picture: https://example.com/staff/colette.jpeg
memberOf: [team-b]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: jenny.doe
spec:
profile:
displayName: Jenny Doe
email: jenny-doe@example.com
picture: https://example.com/staff/jenny.jpeg
memberOf: [team-b]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: jonathon.page
spec:
profile:
displayName: Jonathon Page
email: jonathon-page@example.com
picture: https://example.com/staff/jonathon.jpeg
memberOf: [team-b]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: justine.barrow
spec:
profile:
displayName: Justine Barrow
email: justine-barrow@example.com
picture: https://example.com/staff/justine.jpeg
memberOf: [team-b]
@@ -0,0 +1,66 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: team-c
description: Team C
spec:
type: team
parent: boxoffice
ancestors: [boxoffice, infrastructure, acme-corp]
children: []
descendants: []
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: calum.leavy
spec:
profile:
displayName: Calum Leavy
email: calum-leavy@example.com
picture: https://example.com/staff/calum.jpeg
memberOf: [team-c]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: frank.tiernan
spec:
profile:
displayName: Frank Tiernan
email: frank-tiernan@example.com
picture: https://example.com/staff/frank.jpeg
memberOf: [team-c]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: peadar.macmahon
spec:
profile:
displayName: Peadar MacMahon
email: peadar-macmahon@example.com
picture: https://example.com/staff/peadar.jpeg
memberOf: [team-c]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: sarah.gilroy
spec:
profile:
displayName: Sarah Gilroy
email: sarah-gilroy@example.com
picture: https://example.com/staff/sarah.jpeg
memberOf: [team-c]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: tara.macgovern
spec:
profile:
displayName: Tara MacGovern
email: tara-macgovern@example.com
picture: https://example.com/staff/tara.jpeg
memberOf: [team-c]
@@ -0,0 +1,33 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: team-d
description: Team D
spec:
type: team
parent: boxoffice
ancestors: [boxoffice, infrastructure, acme-corp]
children: []
descendants: []
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: eva.macdowell
spec:
profile:
displayName: Eva MacDowell
email: eva-macdowell@example.com
picture: https://example.com/staff/eva.jpeg
memberOf: [team-d]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: lucy.sheehan
spec:
profile:
displayName: Lucy Sheehan
email: lucy-sheehan@example.com
picture: https://example.com/staff/lucy.jpeg
memberOf: [team-d]
@@ -0,0 +1,12 @@
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: example-apis
description: A collection of all Backstage example APIs
spec:
targets:
- ./apis/hello-world-api.yaml
- ./apis/petstore-api.yaml
- ./apis/spotify-api.yaml
- ./apis/streetlights-api.yaml
- ./apis/swapi-graphql.yaml
@@ -0,0 +1,16 @@
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: example-components
description: A collection of all Backstage example components
spec:
targets:
- ./components/artist-lookup-component.yaml
- ./components/petstore-component.yaml
- ./components/playback-order-component.yaml
- ./components/podcast-api-component.yaml
- ./components/queue-proxy-component.yaml
- ./components/searcher-component.yaml
- ./components/playback-lib-component.yaml
- ./components/www-artist-component.yaml
- ./components/shuffle-api-component.yaml
@@ -0,0 +1,48 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: hello-world
description: Hello World example for gRPC
spec:
type: grpc
lifecycle: deprecated
owner: team-c
definition: |
// Copyright 2015 gRPC 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.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
@@ -0,0 +1,124 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: petstore
description: The petstore API
tags:
- store
- rest
spec:
type: openapi
lifecycle: experimental
owner: team-c
definition: |
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
format: int32
responses:
'200':
description: A paged array of pets
headers:
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
'201':
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
@@ -0,0 +1,17 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: spotify
description: The Spotify web API
tags:
- spotify
- rest
annotations:
# The annotation is deprecated, we use placeholders (see below) instead, remove it later.
backstage.io/definition-at-location: 'url:https://raw.githubusercontent.com/APIs-guru/openapi-directory/master/APIs/spotify.com/v1/swagger.yaml'
spec:
type: openapi
lifecycle: production
owner: team-a
definition:
$text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/spotify.com/v1/swagger.yaml
@@ -0,0 +1,221 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: streetlights
description: The Smartylighting Streetlights API allows you to remotely manage the city lights.
tags:
- mqtt
spec:
type: asyncapi
lifecycle: production
owner: team-c
definition: |
asyncapi: 2.0.0
info:
title: Streetlights API
version: '1.0.0'
description: |
The Smartylighting Streetlights API allows you to remotely manage the city lights.
### Check out its awesome features:
* Turn a specific streetlight on/off 🌃
* Dim a specific streetlight 😎
* Receive real-time information about environmental lighting conditions 📈
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0
servers:
production:
url: api.streetlights.smartylighting.com:{port}
protocol: mqtt
description: Test broker
variables:
port:
description: Secure connection (TLS) is available through port 8883.
default: '1883'
enum:
- '1883'
- '8883'
security:
- apiKey: []
- supportedOauthFlows:
- streetlights:on
- streetlights:off
- streetlights:dim
- openIdConnectWellKnown: []
defaultContentType: application/json
channels:
smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured:
description: The topic on which measured values may be produced and consumed.
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
subscribe:
summary: Receive information about environmental lighting conditions of a particular streetlight.
operationId: receiveLightMeasurement
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/lightMeasured'
smartylighting/streetlights/1/0/action/{streetlightId}/turn/on:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
publish:
operationId: turnOn
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/turnOnOff'
smartylighting/streetlights/1/0/action/{streetlightId}/turn/off:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
publish:
operationId: turnOff
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/turnOnOff'
smartylighting/streetlights/1/0/action/{streetlightId}/dim:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
publish:
operationId: dimLight
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/dimLight'
components:
messages:
lightMeasured:
name: lightMeasured
title: Light measured
summary: Inform about environmental lighting conditions for a particular streetlight.
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: "#/components/schemas/lightMeasuredPayload"
turnOnOff:
name: turnOnOff
title: Turn on/off
summary: Command a particular streetlight to turn the lights on or off.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: "#/components/schemas/turnOnOffPayload"
dimLight:
name: dimLight
title: Dim light
summary: Command a particular streetlight to dim the lights.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: "#/components/schemas/dimLightPayload"
schemas:
lightMeasuredPayload:
type: object
properties:
lumens:
type: integer
minimum: 0
description: Light intensity measured in lumens.
sentAt:
$ref: "#/components/schemas/sentAt"
turnOnOffPayload:
type: object
properties:
command:
type: string
enum:
- on
- off
description: Whether to turn on or off the light.
sentAt:
$ref: "#/components/schemas/sentAt"
dimLightPayload:
type: object
properties:
percentage:
type: integer
description: Percentage to which the light should be dimmed to.
minimum: 0
maximum: 100
sentAt:
$ref: "#/components/schemas/sentAt"
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
securitySchemes:
apiKey:
type: apiKey
in: user
description: Provide your API key as the user and leave the password empty.
supportedOauthFlows:
type: oauth2
description: Flows to support OAuth 2.0
flows:
implicit:
authorizationUrl: 'https://authserver.example/auth'
scopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
password:
tokenUrl: 'https://authserver.example/token'
scopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
clientCredentials:
tokenUrl: 'https://authserver.example/token'
scopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
authorizationCode:
authorizationUrl: 'https://authserver.example/auth'
tokenUrl: 'https://authserver.example/token'
refreshUrl: 'https://authserver.example/refresh'
scopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
openIdConnectWellKnown:
type: openIdConnect
openIdConnectUrl: 'https://authserver.example/.well-known'
parameters:
streetlightId:
description: The ID of the streetlight.
schema:
type: string
messageTraits:
commonHeaders:
headers:
type: object
properties:
my-app-header:
type: integer
minimum: 0
maximum: 100
operationTraits:
kafka:
bindings:
kafka:
clientId: my-app-id
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: artist-lookup
description: Artist Lookup
tags:
- java
- data
spec:
type: service
lifecycle: experimental
owner: team-a
@@ -0,0 +1,13 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: petstore
description: Petstore
spec:
type: service
lifecycle: experimental
owner: team-c
implementsApis:
- petstore
- streetlights
- hello-world
@@ -0,0 +1,9 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: playback-sdk
description: Audio and video playback SDK
spec:
type: library
lifecycle: experimental
owner: team-c
@@ -0,0 +1,12 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: playback-order
description: Playback Order
tags:
- java
- playback
spec:
type: service
lifecycle: production
owner: user:guest
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: podcast-api
description: Podcast API
tags:
- java
spec:
type: service
lifecycle: experimental
owner: team-b
@@ -0,0 +1,12 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: queue-proxy
description: Queue Proxy
tags:
- go
- website
spec:
type: website
lifecycle: production
owner: team-b
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: searcher
description: Searcher
tags:
- go
spec:
type: service
lifecycle: production
owner: user:guest
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: shuffle-api
description: Shuffle API
tags:
- go
spec:
type: service
lifecycle: production
owner: user:guest
@@ -0,0 +1,9 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: www-artist
description: Artist main website
spec:
type: website
lifecycle: production
owner: team-a
@@ -0,0 +1,7 @@
# Example Entities
NOTE: These are being replaced with the contents of the `examples-relative`
sibling directory, after December 16 2020. If you are using this example data
you will need to be using a released version of the catalog backend which
supports relative targets in Location entities
[see #3513](https://github.com/backstage/backstage/pull/3513) after that time.
@@ -42,3 +42,17 @@ spec:
email: nigel-manning@example.com
picture: https://example.com/staff/nigel.jpeg
memberOf: [team-a]
---
# This user is added as an example, to make it more easy for the "Guest"
# sign-in option to demonstrate some entities being owned. In a regular org,
# a guest user would probably not be registered like this.
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: guest
spec:
profile:
displayName: Guest User
email: guest@example.com
picture: https://example.com/staff/the-ceos-dog.jpeg
memberOf: [team-a]
@@ -6,7 +6,7 @@ metadata:
spec:
type: grpc
lifecycle: deprecated
owner: grpc@example.com
owner: team-c
definition: |
// Copyright 2015 gRPC authors.
//
@@ -9,7 +9,7 @@ metadata:
spec:
type: openapi
lifecycle: experimental
owner: pets@example.com
owner: team-c
definition: |
openapi: "3.0.0"
info:
@@ -12,6 +12,6 @@ metadata:
spec:
type: openapi
lifecycle: production
owner: spotify@example.com
owner: team-a
definition:
$text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/spotify.com/v1/swagger.yaml
@@ -8,7 +8,7 @@ metadata:
spec:
type: asyncapi
lifecycle: production
owner: streetlights@example.com
owner: team-c
definition: |
asyncapi: 2.0.0
info:
@@ -6,7 +6,7 @@ metadata:
spec:
type: graphql
lifecycle: production
owner: yoda@dagobah.space
owner: team-b
definition: |
schema {
query: Root
@@ -9,4 +9,4 @@ metadata:
spec:
type: service
lifecycle: experimental
owner: artists@example.com
owner: team-a
@@ -6,7 +6,7 @@ metadata:
spec:
type: service
lifecycle: experimental
owner: pets@example.com
owner: team-c
implementsApis:
- petstore
- streetlights
@@ -6,4 +6,4 @@ metadata:
spec:
type: library
lifecycle: experimental
owner: players@example.com
owner: team-c
@@ -9,4 +9,4 @@ metadata:
spec:
type: service
lifecycle: production
owner: guest
owner: user:guest
@@ -8,4 +8,4 @@ metadata:
spec:
type: service
lifecycle: experimental
owner: players@example.com
owner: team-b
@@ -9,4 +9,4 @@ metadata:
spec:
type: website
lifecycle: production
owner: tools@example.com
owner: team-b
@@ -8,4 +8,4 @@ metadata:
spec:
type: service
lifecycle: production
owner: guest
owner: user:guest
@@ -8,4 +8,4 @@ metadata:
spec:
type: service
lifecycle: production
owner: guest
owner: user:guest
@@ -6,4 +6,4 @@ metadata:
spec:
type: website
lifecycle: production
owner: artists@example.com
owner: team-a
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
"version": "0.3.1",
"version": "0.4.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,7 +29,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.2",
"@backstage/cli": "^0.4.0",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
@@ -32,15 +32,6 @@ const schema = yup.object<Partial<ComponentEntityV1alpha1>>({
implementsApis: yup.array(yup.string().required()).notRequired(),
providesApis: yup.array(yup.string().required()).notRequired(),
consumesApis: yup.array(yup.string().required()).notRequired(),
kubernetes: yup
.object<any>({
selector: yup
.object<any>({
matchLabels: yup.object<any>().required(),
})
.required(),
})
.notRequired(),
})
.required(),
});
@@ -60,13 +51,6 @@ export interface ComponentEntityV1alpha1 extends Entity {
implementsApis?: string[];
providesApis?: string[];
consumesApis?: string[];
kubernetes?: {
selector: {
matchLabels: {
[key: string]: string;
};
};
};
};
}
@@ -54,9 +54,9 @@ describe('LocationV1alpha1Validator', () => {
await expect(validator.check(entity)).resolves.toBe(false);
});
it('rejects missing type', async () => {
it('accepts missing type', async () => {
delete (entity as any).spec.type;
await expect(validator.check(entity)).rejects.toThrow(/type/);
await expect(validator.check(entity)).resolves.toBe(true);
});
it('rejects wrong type', async () => {
@@ -26,7 +26,7 @@ const schema = yup.object<Partial<LocationEntityV1alpha1>>({
kind: yup.string().required().equals([KIND]),
spec: yup
.object({
type: yup.string().required().min(1),
type: yup.string().notRequired().min(1),
target: yup.string().notRequired().min(1),
targets: yup.array(yup.string().required()).notRequired(),
})
@@ -37,7 +37,7 @@ export interface LocationEntityV1alpha1 extends Entity {
apiVersion: typeof API_VERSION[number];
kind: typeof KIND;
spec: {
type: string;
type?: string;
target?: string;
targets?: string[];
};
+16
View File
@@ -1,5 +1,21 @@
# @backstage/cli
## 0.4.0
### Minor Changes
- 00670a96e: sort product panels and navigation menu by greatest cost
update tsconfig.json to use ES2020 api
### Patch Changes
- b4488ddb0: Added a type alias for PositionError = GeolocationPositionError
- 4a655c89d: Bump versions of `esbuild` and `rollup-plugin-esbuild`
- 8a16e8af8: Support `.npmrc` when building with private NPM registries
- Updated dependencies [4e7091759]
- Updated dependencies [b4488ddb0]
- @backstage/config-loader@0.4.0
## 0.3.2
### Patch Changes
+12
View File
@@ -97,3 +97,15 @@ declare module '*.module.sass' {
const classes: { readonly [key: string]: string };
export default classes;
}
// NOTE(freben): Both the fix, and the placement of the fix, are not great.
//
// The fix is because the PositionError was renamed to
// GeolocationPositionError outside of our control, and react-use is dependent
// on the old name.
//
// The placement is because it's the one location we have at the moment, where
// a central .d.ts file is imported by the frontend and can be amended.
//
// After both TS and react-use are bumped high enough, this should be removed.
type PositionError = GeolocationPositionError;
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
"version": "0.3.2",
"version": "0.4.0",
"private": false,
"publishConfig": {
"access": "public"
@@ -30,7 +30,7 @@
"dependencies": {
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.1",
"@backstage/config-loader": "^0.3.0",
"@backstage/config-loader": "^0.4.0",
"@hot-loader/react-dom": "^16.13.0",
"@lerna/package-graph": "^3.18.5",
"@lerna/project": "^3.18.0",
@@ -111,11 +111,11 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/backend-common": "^0.3.2",
"@backstage/backend-common": "^0.3.3",
"@backstage/config": "^0.1.1",
"@backstage/core": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@backstage/dev-utils": "^0.1.5",
"@backstage/test-utils": "^0.1.4",
"@backstage/theme": "^0.2.1",
"@types/diff": "^4.0.2",
"@types/fs-extra": "^9.0.1",
@@ -34,11 +34,15 @@ export default async (cmd: Command) => {
const pkgPath = paths.resolveTarget(PKG_PATH);
const pkg = await fs.readJson(pkgPath);
const appConfigs = await findAppConfigs();
const npmrc = (await fs.pathExists(paths.resolveTargetRoot('.npmrc')))
? ['.npmrc']
: [];
const tempDistWorkspace = await createDistWorkspace([pkg.name], {
buildDependencies: Boolean(cmd.build),
files: [
'package.json',
'yarn.lock',
...npmrc,
...appConfigs,
{ src: paths.resolveTarget('Dockerfile'), dest: 'Dockerfile' },
],
+6 -1
View File
@@ -47,6 +47,7 @@ export function registerCommands(program: CommanderStatic) {
program
.command('backend:__experimental__bundle__', { hidden: true })
.description('Bundle all backend packages into dist-workspace')
.option('--build', 'Build packages before packing them into the image')
.action(lazy(() => import('./backend/bundle').then(m => m.default)));
program
@@ -55,7 +56,11 @@ export function registerCommands(program: CommanderStatic) {
.helpOption(', --backstage-cli-help') // Let docker handle --help
.option('--build', 'Build packages before packing them into the image')
.description(
'Bundles the package into a docker image. All extra args are forwarded to docker image build',
// TODO: Add example use cases in Backstage documentation.
// For example, if a $NPM_TOKEN needs to be exposed, run `backend:build-image --secret
// id=NPM_TOKEN,src=/NPM_TOKEN.txt`.
'Bundles the package into a docker image. All extra args are forwarded to ' +
'`docker image build`.',
)
.action(lazy(() => import('./backend/buildImage').then(m => m.default)));
+14
View File
@@ -1,5 +1,19 @@
# @backstage/config-loader
## 0.4.0
### Minor Changes
- 4e7091759: Fix typo of "visibility" in config schema reference
If you have defined a config element named `visiblity`, you
will need to fix the spelling to `visibility`. For more info,
see https://backstage.io/docs/conf/defining#visibility.
### Patch Changes
- b4488ddb0: Added a type alias for PositionError = GeolocationPositionError
## 0.3.0
### Minor Changes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/config-loader",
"description": "Config loading functionality used by Backstage backend, and CLI",
"version": "0.3.0",
"version": "0.4.0",
"private": false,
"publishConfig": {
"access": "public",
@@ -36,7 +36,7 @@
"fs-extra": "^9.0.0",
"json-schema": "^0.2.5",
"json-schema-merge-allof": "^0.7.0",
"typescript-json-schema": "^0.43.0",
"typescript-json-schema": "^0.45.0",
"yaml": "^1.9.2",
"yup": "^0.29.3"
},
+7
View File
@@ -1,5 +1,12 @@
# @backstage/core-api
## 0.2.4
### Patch Changes
- b4488ddb0: Added a type alias for PositionError = GeolocationPositionError
- @backstage/test-utils@0.1.4
## 0.2.3
### Patch Changes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-api",
"description": "Internal Core API used by Backstage plugins and apps",
"version": "0.2.3",
"version": "0.2.4",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,7 +30,7 @@
},
"dependencies": {
"@backstage/config": "^0.1.1",
"@backstage/test-utils": "^0.1.3",
"@backstage/test-utils": "^0.1.4",
"@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -42,7 +42,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.3.2",
"@backstage/cli": "^0.4.0",
"@backstage/test-utils-core": "^0.1.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
@@ -19,7 +19,7 @@ import { Observable } from '../../../types';
type RequestQueueEntry<ResultType> = {
scopes: Set<string>;
resolve: (value?: ResultType | PromiseLike<ResultType> | undefined) => void;
resolve: (value: ResultType | PromiseLike<ResultType>) => void;
reject: (reason: Error) => void;
};
@@ -0,0 +1,62 @@
/*
* 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.
*/
import React from 'react';
import { attachComponentData, getComponentData } from './componentData';
describe('elementData', () => {
it('should attach a single piece of data', () => {
const data = { foo: 'bar' };
const Component = () => null;
attachComponentData(Component, 'my-data', data);
const element = <Component />;
expect(getComponentData(element, 'my-data')).toBe(data);
});
it('should attach several distinct pieces of data', () => {
const data1 = { foo: 'bar' };
const data2 = { test: 'value' };
const Component = () => null;
attachComponentData(Component, 'my-data', data1);
attachComponentData(Component, 'second', data2);
const element = <Component />;
expect(getComponentData(element, 'my-data')).toBe(data1);
expect(getComponentData(element, 'second')).toBe(data2);
});
it('returns undefined for missing data', () => {
const data = { foo: 'bar' };
const Component1 = () => null;
const Component2 = () => null;
attachComponentData(Component2, 'my-data', data);
const element1 = <Component1 />;
const element2 = <Component2 />;
expect(getComponentData(element1, 'missing')).toBeUndefined();
expect(getComponentData(element2, 'missing')).toBeUndefined();
});
it('should throw when attempting to overwrite data', () => {
const data = { foo: 'bar' };
const MyComponent = () => null;
attachComponentData(MyComponent, 'my-data', data);
expect(() => attachComponentData(MyComponent, 'my-data', data)).toThrow(
'Attempted to attach duplicate data "my-data" to component "MyComponent"',
);
});
});
@@ -0,0 +1,69 @@
/*
* 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.
*/
import { ComponentType, ReactNode } from 'react';
const DATA_KEY = Symbol('backstage-component-data');
type DataContainer = {
map: Map<string, unknown>;
};
type ComponentWithData<P> = ComponentType<P> & {
[DATA_KEY]?: DataContainer;
};
type ReactNodeWithData = ReactNode & {
type?: { [DATA_KEY]?: DataContainer };
};
export function attachComponentData<P>(
component: ComponentType<P>,
type: string,
data: unknown,
) {
const dataComponent = component as ComponentWithData<P>;
let container = dataComponent[DATA_KEY];
if (!container) {
container = dataComponent[DATA_KEY] = { map: new Map() };
}
if (container.map.has(type)) {
const name = component.displayName || component.name;
throw new Error(
`Attempted to attach duplicate data "${type}" to component "${name}"`,
);
}
container.map.set(type, data);
}
export function getComponentData<T>(
node: ReactNode,
type: string,
): T | undefined {
if (!node) {
return undefined;
}
const container = (node as ReactNodeWithData).type?.[DATA_KEY];
if (!container) {
return undefined;
}
return container.map.get(type) as T | undefined;
}
@@ -0,0 +1,72 @@
/*
* 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.
*/
import React from 'react';
import { createPlugin } from '../plugin';
import { createRouteRef } from '../routing';
import { getComponentData } from './componentData';
import {
createComponentExtension,
createReactExtension,
createRoutableExtension,
} from './extensions';
const plugin = createPlugin({
id: 'my-plugin',
});
describe('extensions', () => {
it('should create a react extension with component data', () => {
const Component = () => null;
const extension = createReactExtension({
component: Component,
data: {
myData: { foo: 'bar' },
},
});
const ExtensionComponent = plugin.provide(extension);
const element = <ExtensionComponent />;
expect(getComponentData(element, 'core.plugin')).toBe(plugin);
expect(getComponentData(element, 'myData')).toEqual({ foo: 'bar' });
});
it('should create react extensions of different types', () => {
const Component = () => null;
const routeRef = createRouteRef({ path: '/foo', title: 'Foo' });
const extension1 = createComponentExtension({
component: Component,
});
const extension2 = createRoutableExtension({
component: Component,
mountPoint: routeRef,
});
const ExtensionComponent1 = plugin.provide(extension1);
const ExtensionComponent2 = plugin.provide(extension2);
const element1 = <ExtensionComponent1 />;
const element2 = <ExtensionComponent2 />;
expect(getComponentData(element1, 'core.plugin')).toBe(plugin);
expect(getComponentData(element2, 'core.plugin')).toBe(plugin);
expect(getComponentData(element2, 'core.mountPoint')).toBe(routeRef);
});
});
@@ -0,0 +1,72 @@
/*
* 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.
*/
import React, {
NamedExoticComponent,
ComponentType,
PropsWithChildren,
} from 'react';
import { RouteRef } from '../routing';
import { attachComponentData } from './componentData';
import { Extension, BackstagePlugin } from '../plugin/types';
export function createRoutableExtension<Props extends {}>(options: {
component: ComponentType<Props>;
mountPoint: RouteRef;
// TODO(Rugvip): We want to carry forward the exact props type from the inner component, with
// or without children. ComponentType stops us from doing that though, as it always
// adds children to the props internally. We may want to work around this with custom types.
}): Extension<
NamedExoticComponent<PropsWithChildren<Props & { path?: string }>>
> {
const { component, mountPoint } = options;
return createReactExtension({
component,
data: {
'core.mountPoint': mountPoint,
},
});
}
export function createComponentExtension<Props extends {}>(options: {
component: ComponentType<Props>;
}): Extension<NamedExoticComponent<Props>> {
const { component } = options;
return createReactExtension({ component });
}
export function createReactExtension<Props extends {}>(options: {
component: ComponentType<Props>;
data?: Record<string, unknown>;
}): Extension<NamedExoticComponent<Props>> {
const { component: Component, data = {} } = options;
return {
expose(plugin: BackstagePlugin): NamedExoticComponent<Props> {
const Result = (props: Props) => <Component {...props} />;
attachComponentData(Result, 'core.plugin', plugin);
for (const [key, value] of Object.entries(data)) {
attachComponentData(Result, key, value);
}
const name = Component.displayName || Component.name || 'Component';
if (name) {
Result.displayName = `Extension(${name})`;
}
return Result as NamedExoticComponent<Props>;
},
};
}
+22
View File
@@ -0,0 +1,22 @@
/*
* 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.
*/
export { attachComponentData, getComponentData } from './componentData';
export {
createReactExtension,
createRoutableExtension,
createComponentExtension,
} from './extensions';
@@ -0,0 +1,98 @@
/*
* 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.
*/
import React, { Children, isValidElement } from 'react';
import {
childDiscoverer,
createCollector,
traverseElementTree,
} from './traversal';
describe('discovery', () => {
it('should collect element names', () => {
const root = (
<main>
<div>
<h1>Title</h1>
<p>Text</p>
</div>
<hr />
<div>
<h2>Title</h2>
<span>Text</span>
</div>
</main>
);
const { names } = traverseElementTree({
root,
discoverers: [childDiscoverer],
collectors: {
names: createCollector(Array<string>(), (acc, el) => {
if (typeof el.type === 'string') {
acc.push(el.type);
}
}),
},
});
expect(names).toEqual([
'main',
'div',
'hr',
'div',
'h1',
'p',
'h2',
'span',
]);
});
it('should collect element names while skipping one level of children', () => {
const root = (
<main>
<div>
<h1>Title</h1>
<p>Text</p>
</div>
<hr />
<div>
<h2>Title</h2>
<span>Text</span>
</div>
</main>
);
const { names } = traverseElementTree({
root,
discoverers: [
el =>
Children.toArray(el.props.children).flatMap(child =>
isValidElement(child) ? child?.props?.children : [],
),
],
collectors: {
names: createCollector(Array<string>(), (acc, el) => {
if (typeof el.type === 'string') {
acc.push(el.type);
}
}),
},
});
expect(names).toEqual(['main', 'h1', 'p', 'h2', 'span']);
});
});
@@ -0,0 +1,138 @@
/*
* 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.
*/
import { isValidElement, ReactNode, ReactElement, Children } from 'react';
export type Discoverer = (element: ReactElement) => ReactNode;
export type Collector<Result, Context> = () => {
accumulator: Result;
visit(
accumulator: Result,
element: ReactElement,
parent: ReactElement,
context: Context,
): Context;
};
/**
* A function that allows you to traverse a tree of React elements using
* varying methods to discover child nodes and collect data along the way.
*/
export function traverseElementTree<Results>(options: {
root: ReactElement;
discoverers: Discoverer[];
collectors: { [name in keyof Results]: Collector<Results[name], any> };
}): Results {
const visited = new Set();
const collectors: {
[name in string]: ReturnType<Collector<any, any>>;
} = {};
// Bootstrap all collectors, initializing the accumulators and providing the visitor function
for (const name in options.collectors) {
if (options.collectors.hasOwnProperty(name)) {
collectors[name] = options.collectors[name]();
}
}
// Internal representation of an element in the tree that we're iterating over
type QueueItem = {
node: ReactNode;
parent: ReactElement;
contexts: { [name in string]: unknown };
};
const queue = [
{
node: Children.toArray(options.root),
parent: options.root,
contexts: {},
} as QueueItem,
];
while (queue.length !== 0) {
const { node, parent, contexts } = queue.shift()!;
// While the parent and the element we pass on to collectors and discoverers
// have been validated and are known to be React elements, the child nodes
// emitted by the discoverers are not.
Children.forEach(node, element => {
if (!isValidElement(element)) {
return;
}
if (visited.has(element)) {
const anyType = element?.type as
| { displayName?: string; name?: string }
| undefined;
const name = anyType?.displayName || anyType?.name || String(anyType);
throw new Error(`Visited element ${name} twice`);
}
visited.add(element);
const nextContexts: QueueItem['contexts'] = {};
// Collectors populate their result data using the current node, and compute
// context for the next iteration
for (const name in collectors) {
if (collectors.hasOwnProperty(name)) {
const collector = collectors[name];
nextContexts[name] = collector.visit(
collector.accumulator,
element,
parent,
contexts[name],
);
}
}
// Discoverers provide ways to continue the traversal from the current element
for (const discoverer of options.discoverers) {
const children = discoverer(element);
if (children) {
queue.push({
node: children,
parent: element,
contexts: nextContexts,
});
}
}
});
}
return Object.fromEntries(
Object.entries(collectors).map(([name, c]) => [name, c.accumulator]),
) as Results;
}
export function createCollector<Result, Context>(
initialResult: Result,
visit: ReturnType<Collector<Result, Context>>['visit'],
): Collector<Result, Context> {
return () => ({ accumulator: initialResult, visit });
}
export function childDiscoverer(element: ReactElement): ReactNode {
return element.props?.children;
}
export function routeElementDiscoverer(element: ReactElement): ReactNode {
if (element.props?.path && element.props?.element) {
return element.props?.element;
}
return undefined;
}
+11 -2
View File
@@ -14,10 +14,15 @@
* limitations under the License.
*/
import { PluginConfig, PluginOutput, BackstagePlugin } from './types';
import {
PluginConfig,
PluginOutput,
BackstagePlugin,
Extension,
} from './types';
import { AnyApiFactory } from '../apis';
export class PluginImpl {
export class PluginImpl implements BackstagePlugin {
private storedOutput?: PluginOutput[];
constructor(private readonly config: PluginConfig) {}
@@ -65,6 +70,10 @@ export class PluginImpl {
return this.storedOutput;
}
provide<T>(extension: Extension<T>): T {
return extension.expose(this);
}
toString() {
return `plugin{${this.config.id}}`;
}
@@ -0,0 +1,98 @@
/*
* 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.
*/
import React, { PropsWithChildren } from 'react';
import { createRouteRef } from '../routing';
import { createPlugin } from './Plugin';
import {
createRoutableExtension,
createComponentExtension,
} from '../extensions';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import {
traverseElementTree,
childDiscoverer,
routeElementDiscoverer,
} from '../extensions/traversal';
import { pluginCollector } from './collectors';
const mockConfig = () => ({ path: '/foo', title: 'Foo' });
const MockComponent = ({ children }: PropsWithChildren<{}>) => <>{children}</>;
const pluginA = createPlugin({ id: 'my-plugin-a' });
const pluginB = createPlugin({ id: 'my-plugin-b' });
const pluginC = createPlugin({ id: 'my-plugin-c' });
const ref1 = createRouteRef(mockConfig());
const ref2 = createRouteRef(mockConfig());
const Extension1 = pluginA.provide(
createRoutableExtension({ component: MockComponent, mountPoint: ref1 }),
);
const Extension2 = pluginB.provide(
createRoutableExtension({ component: MockComponent, mountPoint: ref2 }),
);
const Extension3 = pluginA.provide(
createComponentExtension({ component: MockComponent }),
);
const Extension4 = pluginB.provide(
createComponentExtension({ component: MockComponent }),
);
const Extension5 = pluginC.provide(
createComponentExtension({ component: MockComponent }),
);
describe('collection', () => {
it('should collect the plugins', () => {
const root = (
<MemoryRouter>
<Routes>
<Extension1 path="/foo">
<div>
<Extension2 path="/bar/:id">
<div>
<div />
{[<Extension4 key={0} />]}
Some text here shouldn't be a problem
<div />
{null}
<div />
<Extension3 />
</div>
</Extension2>
{false}
{true}
{0}
</div>
</Extension1>
<div>
<Route path="/divsoup" element={<Extension5 />} />
</div>
</Routes>
</MemoryRouter>
);
const { plugins } = traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
plugins: pluginCollector,
},
});
expect(plugins).toEqual(new Set([pluginA, pluginB, pluginC]));
});
});
@@ -0,0 +1,44 @@
/*
* 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.
*/
/*
* 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.
*/
import { BackstagePlugin } from './types';
import { getComponentData } from '../extensions';
import { createCollector } from '../extensions/traversal';
export const pluginCollector = createCollector(
new Set<BackstagePlugin>(),
(acc, node) => {
const plugin = getComponentData<BackstagePlugin>(node, 'core.plugin');
if (plugin) {
acc.add(plugin);
}
},
);
+5
View File
@@ -66,10 +66,15 @@ export type PluginOutput =
| RedirectRouteOutput
| FeatureFlagOutput;
export type Extension<T> = {
expose(plugin: BackstagePlugin): T;
};
export type BackstagePlugin = {
getId(): string;
output(): PluginOutput[];
getApis(): Iterable<AnyApiFactory>;
provide<T>(extension: Extension<T>): T;
};
export type PluginConfig = {
@@ -0,0 +1,193 @@
/*
* 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.
*/
import React, { PropsWithChildren } from 'react';
import { routeCollector, routeParentCollector } from './collectors';
import {
traverseElementTree,
childDiscoverer,
routeElementDiscoverer,
} from '../extensions/traversal';
import { createRouteRef } from './RouteRef';
import { createPlugin } from '../plugin';
import { createRoutableExtension } from '../extensions';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
const mockConfig = () => ({ path: '/foo', title: 'Foo' });
const MockComponent = ({ children }: PropsWithChildren<{}>) => <>{children}</>;
const plugin = createPlugin({ id: 'my-plugin' });
const ref1 = createRouteRef(mockConfig());
const ref2 = createRouteRef(mockConfig());
const ref3 = createRouteRef(mockConfig());
const ref4 = createRouteRef(mockConfig());
const ref5 = createRouteRef(mockConfig());
const Extension1 = plugin.provide(
createRoutableExtension({ component: MockComponent, mountPoint: ref1 }),
);
const Extension2 = plugin.provide(
createRoutableExtension({ component: MockComponent, mountPoint: ref2 }),
);
const Extension3 = plugin.provide(
createRoutableExtension({ component: MockComponent, mountPoint: ref3 }),
);
const Extension4 = plugin.provide(
createRoutableExtension({ component: MockComponent, mountPoint: ref4 }),
);
const Extension5 = plugin.provide(
createRoutableExtension({ component: MockComponent, mountPoint: ref5 }),
);
describe('discovery', () => {
it('should collect routes', () => {
const list = [
<div key={0} />,
<div key={1} />,
<div key={3}>
<Extension5 path="/blop" />
</div>,
];
const root = (
<MemoryRouter>
<Routes>
<Extension1 path="/foo">
<div>
<Extension2 path="/bar/:id">
<div>
<div />
Some text here shouldn't be a problem
<div />
{null}
<div />
<Extension3 path="/baz" />
</div>
</Extension2>
{false}
{list}
{true}
{0}
</div>
</Extension1>
<div>
<Route path="/divsoup" element={<Extension4 />} />
</div>
</Routes>
</MemoryRouter>
);
const { routes, routeParents } = traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routes: routeCollector,
routeParents: routeParentCollector,
},
});
expect(routes).toEqual(
new Map([
[ref1, '/foo'],
[ref2, '/bar/:id'],
[ref3, '/baz'],
[ref4, '/divsoup'],
[ref5, '/blop'],
]),
);
expect(routeParents).toEqual(
new Map([
[ref1, undefined],
[ref2, ref1],
[ref3, ref2],
[ref4, undefined],
[ref5, ref1],
]),
);
});
it('should handle all react router Route patterns', () => {
const root = (
<MemoryRouter>
<Routes>
<Route
path="/foo"
element={
<Extension1>
<Routes>
<Extension2 path="/bar/:id" />
</Routes>
</Extension1>
}
/>
<Route path="/baz" element={<Extension3 path="/not-used" />}>
<Route path="/divsoup" element={<Extension4 />} />
<Extension5 path="/blop" />
</Route>
</Routes>
</MemoryRouter>
);
const { routes, routeParents } = traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routes: routeCollector,
routeParents: routeParentCollector,
},
});
expect(routes).toEqual(
new Map([
[ref1, '/foo'],
[ref2, '/bar/:id'],
[ref3, '/baz'],
[ref4, '/divsoup'],
[ref5, '/blop'],
]),
);
expect(routeParents).toEqual(
new Map([
[ref1, undefined],
[ref2, ref1],
[ref3, undefined],
[ref4, ref3],
[ref5, ref3],
]),
);
});
it('should not visit the same element twice', () => {
const element = <Extension3 path="/baz" />;
expect(() =>
traverseElementTree({
root: (
<MemoryRouter>
<Extension1 path="/foo">{element}</Extension1>
<Extension2 path="/bar">{element}</Extension2>
</MemoryRouter>
),
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routes: routeCollector,
routeParents: routeParentCollector,
},
}),
).toThrow(`Visited element Extension(MockComponent) twice`);
});
});

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