diff --git a/.changeset/calm-elephants-kiss.md b/.changeset/calm-elephants-kiss.md deleted file mode 100644 index 063ff00e26..0000000000 --- a/.changeset/calm-elephants-kiss.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/cli': patch ---- - -bump node-tar version to the latest diff --git a/.changeset/chilled-schools-raise.md b/.changeset/chilled-schools-raise.md new file mode 100644 index 0000000000..56058b6125 --- /dev/null +++ b/.changeset/chilled-schools-raise.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Make sure that the unique databases names generated by `TestDatabases.create()` +don't collide. diff --git a/.changeset/clean-jokes-think.md b/.changeset/clean-jokes-think.md new file mode 100644 index 0000000000..a4fc65e6fc --- /dev/null +++ b/.changeset/clean-jokes-think.md @@ -0,0 +1,18 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Expose a new composable `TechDocsIndexPage` and a `DefaultTechDocsHome` with support for starring docs and filtering on owned, starred, owner, and tags. + +You can migrate to the new UI view by making the following changes in your `App.tsx`: + +```diff +- } /> ++ }> ++ ++ ++ } ++ /> +``` diff --git a/.changeset/curvy-poets-dance.md b/.changeset/curvy-poets-dance.md deleted file mode 100644 index abf478a729..0000000000 --- a/.changeset/curvy-poets-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Keep track of filtered configuration values when running frontend in development mode. diff --git a/.changeset/eighty-glasses-mate.md b/.changeset/eighty-glasses-mate.md new file mode 100644 index 0000000000..d71ef8fd42 --- /dev/null +++ b/.changeset/eighty-glasses-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Use a more informative error message when URL reading isn't allowed due to no reader matching the target URL. diff --git a/.changeset/fluffy-cats-approve.md b/.changeset/fluffy-cats-approve.md deleted file mode 100644 index dff0972249..0000000000 --- a/.changeset/fluffy-cats-approve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -fix typo in the comments of EntityPage component diff --git a/.changeset/four-buttons-design.md b/.changeset/four-buttons-design.md new file mode 100644 index 0000000000..172d011057 --- /dev/null +++ b/.changeset/four-buttons-design.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Adds ability to send an ExternalId with the assume role request to AWS diff --git a/.changeset/four-planes-retire.md b/.changeset/four-planes-retire.md deleted file mode 100644 index 6a2a8f6bc5..0000000000 --- a/.changeset/four-planes-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Fix bug with re-rendering the EntityRoutes when the entity changes but the route does not diff --git a/.changeset/great-cherries-fly.md b/.changeset/great-cherries-fly.md deleted file mode 100644 index 3480acd7be..0000000000 --- a/.changeset/great-cherries-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-xcmetrics': patch ---- - -New page for browsing all builds with filtering and pagination capabilities diff --git a/.changeset/green-ducks-tan.md b/.changeset/green-ducks-tan.md new file mode 100644 index 0000000000..e096bfeb36 --- /dev/null +++ b/.changeset/green-ducks-tan.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Added UI Schema support for array items for example, support EntityPicker within an array field diff --git a/.changeset/happy-lies-wink.md b/.changeset/happy-lies-wink.md new file mode 100644 index 0000000000..709cd3862e --- /dev/null +++ b/.changeset/happy-lies-wink.md @@ -0,0 +1,22 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +- Adds a new field `EntityNamePicker` that can be used in scaffolder templates to accept and validate an entity name. This field is registered by default, and can be used in templates by setting the `ui:field` property to `EntityNamePicker`. If you've customized your scaffolder field extensions, you can include this one by adding it when registering the scaffolder route: + +```diff +import { + ScaffolderFieldExtensions, ++ EntityNamePickerFieldExtension, +} from '@backstage/plugin-scaffolder'; + + }> + + {/* ...custom field extensions... */} + ++ + + ; +``` + +- Adds a new generic field `TextValuePicker` to be used when writing custom field extensions that use a standard UI with custom validation. An example of doing this can be found in `packages/app/src/components/scaffolder/customScaffolderExtensions.tsx`. diff --git a/.changeset/large-flies-bow.md b/.changeset/large-flies-bow.md deleted file mode 100644 index e571bc9a3c..0000000000 --- a/.changeset/large-flies-bow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config': patch ---- - -Add warning when trying to access configuration values that have been filtered out by visibility. diff --git a/.changeset/late-frogs-yell.md b/.changeset/late-frogs-yell.md new file mode 100644 index 0000000000..b3e45785e3 --- /dev/null +++ b/.changeset/late-frogs-yell.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Add support for serving the app with a base path other than `/`, which is enabled by including the path in `app.baseUrl`. diff --git a/.changeset/little-tools-melt.md b/.changeset/little-tools-melt.md new file mode 100644 index 0000000000..5e55d27b7a --- /dev/null +++ b/.changeset/little-tools-melt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-sonarqube': minor +--- + +Use IdentityApi to provide Auth Token for SonarQubeClient Api calls diff --git a/.changeset/lucky-spiders-impress.md b/.changeset/lucky-spiders-impress.md deleted file mode 100644 index 270ecf7a7c..0000000000 --- a/.changeset/lucky-spiders-impress.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-components': patch -'@backstage/core-plugin-api': patch ---- - -Switched frontend identity code to use `token` instead of the deprecated `idToken` field diff --git a/.changeset/modern-ghosts-dress.md b/.changeset/modern-ghosts-dress.md new file mode 100644 index 0000000000..6d3cdbfd3c --- /dev/null +++ b/.changeset/modern-ghosts-dress.md @@ -0,0 +1,25 @@ +--- +'@backstage/core-app-api': patch +'@backstage/core-components': patch +'@backstage/core-plugin-api': patch +'@backstage/dev-utils': patch +'@backstage/test-utils': patch +'@backstage/test-utils-core': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-cost-insights': patch +'@backstage/plugin-explore': patch +'@backstage/plugin-git-release-manager': patch +'@backstage/plugin-lighthouse': patch +'@backstage/plugin-pagerduty': patch +'@backstage/plugin-rollbar': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-search': patch +'@backstage/plugin-sentry': patch +'@backstage/plugin-tech-radar': patch +'@backstage/plugin-techdocs': patch +--- + +Switched `@types/react` dependency to request `*` rather than a specific version. diff --git a/.changeset/neat-meals-arrive.md b/.changeset/neat-meals-arrive.md new file mode 100644 index 0000000000..292d98de26 --- /dev/null +++ b/.changeset/neat-meals-arrive.md @@ -0,0 +1,12 @@ +--- +'@backstage/create-app': patch +--- + +Updated the index page redirect to work with apps served on a different base path than `/`. + +To apply this change to an existing app, remove the `/` prefix from the target route in the `Navigate` element in `packages/app/src/App.tsx`: + +```diff +- ++ +``` diff --git a/.changeset/ninety-worms-kick.md b/.changeset/ninety-worms-kick.md new file mode 100644 index 0000000000..f350c6eb31 --- /dev/null +++ b/.changeset/ninety-worms-kick.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Adds an allow list of GitHub installations diff --git a/.changeset/pink-pugs-relate.md b/.changeset/pink-pugs-relate.md new file mode 100644 index 0000000000..e9de00901e --- /dev/null +++ b/.changeset/pink-pugs-relate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Switch `EventSource` implementation with header support from a Node.js API-based one to an XHR-based one. diff --git a/.changeset/plenty-carpets-jog.md b/.changeset/plenty-carpets-jog.md new file mode 100644 index 0000000000..f4586072a7 --- /dev/null +++ b/.changeset/plenty-carpets-jog.md @@ -0,0 +1,18 @@ +--- +'@backstage/create-app': patch +--- + +Use new composable `TechDocsIndexPage` and `DefaultTechDocsHome` + +Make the following changes to your `App.tsx` to migrate existing apps: + +```diff +- } /> ++ }> ++ ++ ++ } ++ /> +``` diff --git a/.changeset/purple-comics-fold.md b/.changeset/purple-comics-fold.md deleted file mode 100644 index f5e02d64db..0000000000 --- a/.changeset/purple-comics-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -Add configuration option to the kubernetes object types. Config option is under `kubernetes.resources`. Defaults to ['pods', 'services', 'configmaps', 'deployments', 'replicasets', 'horizontalpodautoscalers', 'ingresses'] diff --git a/.changeset/purple-rocks-explode.md b/.changeset/purple-rocks-explode.md deleted file mode 100644 index 86f575e317..0000000000 --- a/.changeset/purple-rocks-explode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Fixed bug for comparing Organization name in `GithubCredentialsProvider` diff --git a/.changeset/quick-coats-peel.md b/.changeset/quick-coats-peel.md deleted file mode 100644 index 12e567aa24..0000000000 --- a/.changeset/quick-coats-peel.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/core-components': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-scaffolder': patch ---- - -Move the `CreateComponentButton` from the catalog plugin to the `core-components` & rename it to `CreateButton` to be reused inside the api-docs plugin & scaffolder plugin, but also future plugins. Additionally, improve responsiveness of `CreateButton` & `SupportButton` by shrinking them to `IconButtons` on smaller screens. diff --git a/.changeset/quick-fans-smash.md b/.changeset/quick-fans-smash.md new file mode 100644 index 0000000000..b394b2af23 --- /dev/null +++ b/.changeset/quick-fans-smash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Fix to allow optionally reading `auth` parameter for custom hosted ElasticSearch instances. Also remove `bearer` auth config since it's currently unsupported. diff --git a/.changeset/search-early-horses-sip.md b/.changeset/search-early-horses-sip.md new file mode 100644 index 0000000000..ce5612ae86 --- /dev/null +++ b/.changeset/search-early-horses-sip.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-backend-module-pg': patch +--- + +Add `plugin-search-backend-module-pg` providing a postgres based search engine. +See the [README of `search-backend-module-pg`](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-pg/README.md) for usage instructions. diff --git a/.changeset/search-lucky-roses-wash.md b/.changeset/search-lucky-roses-wash.md deleted file mode 100644 index f2445ddc8d..0000000000 --- a/.changeset/search-lucky-roses-wash.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-search': patch ---- - -Change `` design to follow Figma and be similar to existing multi -selects in Backstage. diff --git a/.changeset/serious-kiwis-wonder.md b/.changeset/serious-kiwis-wonder.md new file mode 100644 index 0000000000..0deba49616 --- /dev/null +++ b/.changeset/serious-kiwis-wonder.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Remove repo restriction from GitHub credentials provider diff --git a/.changeset/shaggy-days-peel.md b/.changeset/shaggy-days-peel.md new file mode 100644 index 0000000000..b35d35f45c --- /dev/null +++ b/.changeset/shaggy-days-peel.md @@ -0,0 +1,6 @@ +--- +'@backstage/cli': patch +'@backstage/create-app': patch +--- + +Migrate to using `webpack@5` πŸŽ‰ diff --git a/.changeset/sharp-berries-fail.md b/.changeset/sharp-berries-fail.md new file mode 100644 index 0000000000..a713b3fdee --- /dev/null +++ b/.changeset/sharp-berries-fail.md @@ -0,0 +1,34 @@ +--- +'@backstage/create-app': patch +--- + +Add a complete prettier setup to the created project. Prettier used to only be added as a dependency to create apps, but there wasn't a complete setup included that makes it easy to run prettier. That has now changed, and the new `prettier:check` command can be used to check the formatting of the files in your created project. + +To apply this change to an existing app, a couple of changes need to be made. + +Create a `.prettierignore` file at the root of your repository with the following contents: + +``` +dist +dist-types +coverage +.vscode +``` + +Next update the root `package.json` by bumping the prettier version and adding the new `prettier:check` command: + +```diff + "scripts": { + ... ++ "prettier:check": "prettier --check .", + ... + }, + ... + "dependencies": { + ... +- "prettier": "^1.19.1" ++ "prettier": "^2.3.2" + } +``` + +Finally run `yarn prettier --write .` on your project to update the existing formatting. diff --git a/.changeset/sharp-donkeys-push.md b/.changeset/sharp-donkeys-push.md new file mode 100644 index 0000000000..fd6304118e --- /dev/null +++ b/.changeset/sharp-donkeys-push.md @@ -0,0 +1,12 @@ +--- +'@backstage/create-app': patch +--- + +Removed the `/` prefix in the catalog `SidebarItem` element, as it is no longer needed. + +To apply this change to an existing app, remove the `/` prefix from the catalog and any other sidebar items in `packages/app/src/components/Root/Root.ts`: + +```diff +- ++ +``` diff --git a/.changeset/sharp-seals-explode.md b/.changeset/sharp-seals-explode.md deleted file mode 100644 index 7dbcac7094..0000000000 --- a/.changeset/sharp-seals-explode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -Add option to populate the `filteredKeys` property when processing configuration with a schema. diff --git a/.changeset/small-actors-sing.md b/.changeset/small-actors-sing.md new file mode 100644 index 0000000000..40de0f363f --- /dev/null +++ b/.changeset/small-actors-sing.md @@ -0,0 +1,12 @@ +--- +'@backstage/create-app': patch +--- + +Switched `@types/react-dom` dependency to of the app package to request `*` rather than a specific version. + +To apply this change to an existing app, change the following in `packages/app/package.json`: + +```diff +- "@types/react-dom": "^16.9.8", ++ "@types/react-dom": "*", +``` diff --git a/.changeset/small-guests-invite.md b/.changeset/small-guests-invite.md new file mode 100644 index 0000000000..507e0b3d12 --- /dev/null +++ b/.changeset/small-guests-invite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Support deep linking in OpenAPI definitions. diff --git a/.changeset/soft-news-float.md b/.changeset/soft-news-float.md deleted file mode 100644 index 03eaf792c7..0000000000 --- a/.changeset/soft-news-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Add the `scaffolder.defaultCommitMessage`, which defaults to `Initial commit`, so it can be customized. diff --git a/.changeset/spicy-crews-applaud.md b/.changeset/spicy-crews-applaud.md new file mode 100644 index 0000000000..d2707516d2 --- /dev/null +++ b/.changeset/spicy-crews-applaud.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-react': patch +--- + +Move and rename `FavoriteEntity` component to `catalog-react` diff --git a/.changeset/stale-goats-chew.md b/.changeset/stale-goats-chew.md deleted file mode 100644 index 775cd827d1..0000000000 --- a/.changeset/stale-goats-chew.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/core-components': patch -'@backstage/plugin-cost-insights': patch -'@backstage/plugin-fossa': patch -'@backstage/plugin-kubernetes': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-pagerduty': patch -'@backstage/plugin-sonarqube': patch -'@backstage/plugin-splunk-on-call': patch -'@backstage/plugin-techdocs': patch ---- - -Fix All Material UI Warnings diff --git a/.changeset/techdocs-chocolate-cake.md b/.changeset/techdocs-chocolate-cake.md deleted file mode 100644 index 36abca944f..0000000000 --- a/.changeset/techdocs-chocolate-cake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Avoid sanitize safe links in the header of document pages. diff --git a/.changeset/techdocs-give-it-all-back.md b/.changeset/techdocs-give-it-all-back.md deleted file mode 100644 index 813f88ba87..0000000000 --- a/.changeset/techdocs-give-it-all-back.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@backstage/techdocs-common': minor ---- - -TechDocs has dropped all support for the long-ago deprecated git-based common -prepares as well as all corresponding values in `backstage.io/techdocs-ref` -annotations. - -Entities whose `backstage.io/techdocs-ref` annotation values still begin with -`github:`, `gitlab:`, `bitbucket:`, or `azure/api:` will no longer be generated -by TechDocs. Be sure to update these values so that they align with their -expected format and your usage of TechDocs. - -For details, see [this explainer on TechDocs ref annotation values][how]. - -[how]: https://backstage.io/docs/features/techdocs/how-to-guides#how-to-understand-techdocs-ref-annotation-values diff --git a/.changeset/techdocs-if-i-could.md b/.changeset/techdocs-if-i-could.md deleted file mode 100644 index e1b04735ed..0000000000 --- a/.changeset/techdocs-if-i-could.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/create-app': patch ---- - -The recommended value for a `backstage.io/techdocs-ref` annotation is now -`dir:.`, indicating "documentation source files are located in the same -directory relative to the catalog entity." Note that `url:` values -are still supported. diff --git a/.changeset/techdocs-spicy-icons-explode.md b/.changeset/techdocs-spicy-icons-explode.md deleted file mode 100644 index 7a532c0323..0000000000 --- a/.changeset/techdocs-spicy-icons-explode.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Update `techdocs.generators` with the latest `techdocs.generator` config in `app-config.yaml`. See -https://backstage.io/docs/features/techdocs/configuration for reference and relevant PR -https://github.com/backstage/backstage/pull/6071/files for the changes. diff --git a/.changeset/techdocs-stale-breakfast-king.md b/.changeset/techdocs-stale-breakfast-king.md new file mode 100644 index 0000000000..cdbdb4d0ad --- /dev/null +++ b/.changeset/techdocs-stale-breakfast-king.md @@ -0,0 +1,19 @@ +--- +'@backstage/techdocs-common': patch +--- + +Stale TechDocs content (files that had previously been published but which have +since been removed) is now removed from storage at publish-time. This is now +supported by the following publishers: + +- Google GCS +- AWS S3 +- Azure Blob Storage + +You may need to apply a greater level of permissions (e.g. the ability to +delete objects in your storage provider) to any credentials/accounts used by +the TechDocs CLI or TechDocs backend in order for this change to take effect. + +For more details, see [#6132][issue-ref]. + +[issue-ref]: https://github.com/backstage/backstage/issues/6132 diff --git a/.changeset/two-ravens-warn.md b/.changeset/two-ravens-warn.md deleted file mode 100644 index 0b82883b9d..0000000000 --- a/.changeset/two-ravens-warn.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -'@backstage/search-common': patch -'@backstage/plugin-search-backend-node': patch ---- - -Implements configuration and indexing functionality for ElasticSearch search engine. Adds indexing, searching and default translator for ElasticSearch and modifies default backend example-app to use ES if it is configured. - -## Example configurations: - -### AWS - -Using AWS hosted ElasticSearch the only configuration options needed is the URL to the ElasticSearch service. The implementation assumes -that environment variables for AWS access key id and secret access key are defined in accordance to the [default AWS credential chain.](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html). - -```yaml -search: - elasticsearch: - provider: aws - node: https://my-backstage-search-asdfqwerty.eu-west-1.es.amazonaws.com -``` - -### Elastic.co - -Elastic Cloud hosted ElasticSearch uses a Cloud ID to determine the instance of hosted ElasticSearch to connect to. Additionally, username and password needs to be provided either directly or using environment variables like defined in [Backstage documentation.](https://backstage.io/docs/conf/writing#includes-and-dynamic-data) - -```yaml -search: - elasticsearch: - provider: elastic - cloudId: backstage-elastic:asdfqwertyasdfqwertyasdfqwertyasdfqwerty== - auth: - username: elastic - password: changeme -``` - -### Others - -Other ElasticSearch instances can be connected to by using standard ElasticSearch authentication methods and exposed URL, provided that the cluster supports that. The configuration options needed are the URL to the node and authentication information. Authentication can be handled by either providing username/password or and API key or a bearer token. In case both username/password combination and one of the tokens are provided, token takes precedence. For more information how to create an API key, see [Elastic documentation on API keys](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html) and how to create a bearer token, see [Elastic documentation on tokens.](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-service-token.html) - -#### Configuration examples - -##### With username and password - -```yaml -search: - elasticsearch: - node: http://localhost:9200 - auth: - username: elastic - password: changeme -``` - -##### With bearer token - -```yaml -search: - elasticsearch: - node: http://localhost:9200 - auth: - bearer: token -``` - -##### With API key - -```yaml -search: - elasticsearch: - node: http://localhost:9200 - auth: - apiKey: base64EncodedKey -``` diff --git a/.changeset/violet-bobcats-wave.md b/.changeset/violet-bobcats-wave.md new file mode 100644 index 0000000000..339d7e0e74 --- /dev/null +++ b/.changeset/violet-bobcats-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Fix for `SidebarItem` matching the active route too broadly. diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index f098788ae6..6f28680ecb 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -223,6 +223,7 @@ Routable rst rsync ruleset +runbook sam sanitization scaffolded diff --git a/.github/workflows/chromatic-storybook-test.yml b/.github/workflows/chromatic-storybook-test.yml index d189236987..043777601f 100644 --- a/.github/workflows/chromatic-storybook-test.yml +++ b/.github/workflows/chromatic-storybook-test.yml @@ -50,4 +50,5 @@ jobs: # projetToken intentionally shared to allow collaborators to run Chromatic on forks # https://www.chromatic.com/docs/custom-ci-provider#run-chromatic-on-external-forks-of-open-source-projects projectToken: 9tzak77m9nj - storybookBuildDir: 'packages/storybook/dist' + workingDir: 'packages/storybook' + storybookBuildDir: 'dist' diff --git a/.github/workflows/e2e-win.yml b/.github/workflows/e2e-win.yml index 1844700cea..3e2fbe0863 100644 --- a/.github/workflows/e2e-win.yml +++ b/.github/workflows/e2e-win.yml @@ -25,6 +25,13 @@ jobs: name: Node ${{ matrix.node-version }} on ${{ matrix.os }} steps: + # In order to have the create-app template function as if it was downloaded from NPM + # we need to make sure we checkout files with LF line endings only + - name: Set git to use LF + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - uses: actions/checkout@v2 - name: use node.js ${{ matrix.node-version }} diff --git a/.imgbotconfig b/.imgbotconfig index f337e087dc..2f4af2ed5e 100644 --- a/.imgbotconfig +++ b/.imgbotconfig @@ -1,5 +1,3 @@ { - "ignoredFiles": [ - "docs/assets/**/*.svg" - ] + "ignoredFiles": ["docs/assets/**/*.svg"] } diff --git a/ADOPTERS.md b/ADOPTERS.md index 6ab915bedb..72b6cce072 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,43 +1,44 @@ -| Organization | Contact | Description of Use | -| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | -| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | -| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | -| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | -| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | -| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | -| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | -| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | -| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | -| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | -| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | -| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | -| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | -| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | -| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | -| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | -| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | -| [Trendyol](https://trendyol.com) | [Erdogan Oksuz](https://github.com/erdoganoksuz) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | -| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | -| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | -| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | -| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | -| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | -| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | -| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | -| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | -| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process πŸŒ•πŸš€πŸ§‘β€πŸš€ | -| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | -| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | -| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | -| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | -| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | -| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | -| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | -| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | -| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | +| Organization | Contact | Description of Use | +| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | +| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | +| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | +| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | +| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | +| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | +| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | +| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | +| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | +| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | +| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | +| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | +| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | +| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | +| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit | +| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | +| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | +| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | +| [Trendyol](https://trendyol.com) | [Erdogan Oksuz](https://github.com/erdoganoksuz) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | +| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | +| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | +| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | +| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | +| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. | +| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | +| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | +| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | +| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process πŸŒ•πŸš€πŸ§‘β€πŸš€ | +| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | +| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | +| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | +| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | +| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | +| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | +| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | +| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. | +| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | +| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2caa8c9c9c..466c77c575 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,7 +116,7 @@ In general, changesets are not needed for the documentation, build utilities, co 1. Run `yarn changeset` 2. Select which packages you want to include a changeset for -3. Select impact of change that you're introducing (patch, minor, or major) +3. Select impact of change that you're introducing, using `minor` for breaking changes and `patch` otherwise. We do not use `major` changes while packages are at version `0.x`. 4. Add generated changeset to Git 5. Push the commit with your changeset to the branch associated with your PR 6. Accept our gratitude for making the release process easier on the maintainers diff --git a/README.md b/README.md index 98da194c1b..1078631926 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ # [Backstage](https://backstage.io) -_During the month of July the majority of the maintainers will be on summer vacation πŸ–οΈ Development will continue as usual, but expect a slower pace for discussions and PR reviews. Why not take this opportunity to [build a plugin](https://backstage.io/docs/plugins/)?_ - [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![CNCF Status](https://img.shields.io/badge/cncf%20status-sandbox-blue.svg)](https://www.cncf.io/projects) [![Main CI Build](https://github.com/backstage/backstage/workflows/Main%20Master%20Build/badge.svg)](https://github.com/backstage/backstage/actions?query=workflow%3A%22Main+Master+Build%22) @@ -63,3 +61,9 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how Copyright 2020-2021 Β© The Backstage Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + +## Security + +Please report sensitive security issues via Spotify's [bug-bounty program](https://hackerone.com/spotify) rather than GitHub. + +For further details please see our complete [security release process](SECURITY.md). diff --git a/SECURITY.md b/SECURITY.md index 3ec51b4210..b410404bcf 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,3 +9,20 @@ ## Reporting a Vulnerability Please report sensitive security issues via Spotify's [bug-bounty program](https://hackerone.com/spotify) rather than GitHub. + +If you have questions about a potential vulnerability, please reach out on Discord by asking for a maintainer in the `#support` channel, or via direct message to a maintainer. + +## Remediation and Notification Process + +Vulnerabilities are handled and published through [GitHub Security Advisories](https://docs.github.com/en/code-security/security-advisories/about-github-security-advisories). + +In the event of a vulnerability the runbook for the maintainers is as follows: + +1. Create a [new draft security advisory](https://github.com/backstage/backstage/security/advisories/new). The values and descriptions don't need to be perfect to begin with as they can be edited later. For severity, use the "Assess severity using CVSS" and refer to [the guide](https://www.first.org/cvss/v3.1/user-guide) for help. +2. Request a CVE identification number. It can take up to 72h for one to be assigned so be sure to do this early on. +3. If there is not already a patch for the vulnerability, collaborate on one in a private fork: + 1. Head to the security advisory on GitHub and [create a private fork](https://docs.github.com/en/code-security/security-advisories/collaborating-in-a-temporary-private-fork-to-resolve-a-security-vulnerability) + 1. [Invite any collaborators](https://docs.github.com/en/code-security/security-advisories/adding-a-collaborator-to-a-security-advisory) from outside the maintainer team that should be part of creating a fix. + 1. Create one or multiple Pull Requests with fixes towards the fork. Note that these PRs will not have CI checks run on them, so more care should be taken to run local validation. The PRs are also not merged like normal PRs, but are instead merged straight into the main repo all at once when the merge button is hit on the security advisory. +4. Once the fix is ready in a PR or private fork and it is time to release it, there are a couple of options. Either merge into the main branch with a changeset and wait for a regular release, or do a quick release by manually bumping the version in `package.json` of the affected package, along with a manual `CHANGELOG.md` entry. Note that a quick release will only work if the package does not have any other pending changes that depend on pending changes in other packages, so be sure to manually check that first, and fall back to an early regular release if needed. In general it's best to stick with the regular release flow, with the quick release being used only for time sensitive fixes. +5. Finalize and publish the security advisory. Note that once you hit the publish button it's no longer possible to edit the advisory. Just like the CVE number this can take up to 72h, and expect it to be slower than the CVE number request. diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index 56586cbfc6..472a6a0abc 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -244,9 +244,7 @@ export interface OAuthProviderHandlers { req: express.Request, options: Record, ): Promise; - handler( - req: express.Request, - ): Promise<{ + handler(req: express.Request): Promise<{ response: AuthResponse; refreshToken?: string; }>; diff --git a/docs/features/search/architecture.md b/docs/features/search/architecture.md index 0a1ebea4b0..4fac0e7cff 100644 --- a/docs/features/search/architecture.md +++ b/docs/features/search/architecture.md @@ -4,8 +4,6 @@ title: Search Architecture description: Documentation on Search Architecture --- -# Search Architecture - > _This architecture has not been fully implemented yet. Find our milestones to > follow our progress and help contribute on the > [Search Roadmap](./README.md#project-roadmap)._ diff --git a/docs/features/search/concepts.md b/docs/features/search/concepts.md index 5da7c9c58f..8cb17b12e6 100644 --- a/docs/features/search/concepts.md +++ b/docs/features/search/concepts.md @@ -4,8 +4,6 @@ title: Search Concepts description: Documentation on Backstage Search Concepts --- -# Search Concepts - Backstage Search lets you find the right information you are looking for in the Backstage ecosystem. diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md index 79ef267ea5..aa40c1e539 100644 --- a/docs/features/search/getting-started.md +++ b/docs/features/search/getting-started.md @@ -4,8 +4,6 @@ title: Getting Started with Search description: How to set up and install Backstage Search --- -# Getting Started - Search functions as a plugin to Backstage, so you will need to use Backstage to use Search. diff --git a/docs/features/search/search-engines.md b/docs/features/search/search-engines.md index a683c8ad84..69a223b29e 100644 --- a/docs/features/search/search-engines.md +++ b/docs/features/search/search-engines.md @@ -4,8 +4,6 @@ title: Search Engines description: Choosing and configuring your search engine for Backstage --- -# Search Engines - Backstage supports 2 search engines by default, an in-memory engine called Lunr and ElasticSearch. You can configure your own search engines by implementing the provided interface as mentioned in the @@ -18,7 +16,7 @@ QueryTranslator interface. This modification can be done without touching provided search engines by using the exposed setter to set the modified query translator into the instance. -``` +```typescript const searchEngine = new LunrSearchEngine({ logger }); searchEngine.setTranslator(new MyNewAndBetterQueryTranslator()); ``` @@ -36,6 +34,35 @@ const searchEngine = new LunrSearchEngine({ logger }); const indexBuilder = new IndexBuilder({ logger, searchEngine }); ``` +## Postgres + +The Postgres based search engine only requires that postgres being configured as +the database engine for Backstage. Therefore it targets setups that want to +avoid maintaining another external service like elastic search. The search +provides decent results and performs well with ten thousands of indexed +documents. The connection to postgres is established via the database manager +also used by other plugins. + +> **Important**: The search plugin requires at least Postgres 11! + +To use the `PgSearchEngine`, make sure that you have a Postgres database +configured and make the following changes to your backend: + +1. Add a dependency on `@backstage/plugin-search-backend-module-pg` to your + backend's `package.json`. +2. Initialize the search engine. It is recommended to initialize it with a + fallback to the lunr search engine if you are running Backstage for + development locally with SQLite: + +```typescript +// In packages/backend/src/plugins/search.ts + +// Initialize a connection to a search engine. +const searchEngine = (await PgSearchEngine.supported(database)) + ? await PgSearchEngine.from({ database }) + : new LunrSearchEngine({ logger }); +``` + ## ElasticSearch Backstage supports ElasticSearch search engine connections, indexing and @@ -103,12 +130,9 @@ Other ElasticSearch instances can be connected to by using standard ElasticSearch authentication methods and exposed URL, provided that the cluster supports that. The configuration options needed are the URL to the node and authentication information. Authentication can be handled by either providing -username/password or an API key or a bearer token. In case both -username/password combination and one of the tokens are provided, token takes -precedence. For more information how to create an API key, see -[Elastic documentation on API keys](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html), -and how to create a bearer token see -[Elastic documentation on tokens.](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-service-token.html) +username/password or an API key. For more information how to create an API key, +see +[Elastic documentation on API keys](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html). #### Configuration examples @@ -123,16 +147,6 @@ search: password: changeme ``` -##### With bearer token - -```yaml -search: - elasticsearch: - node: http://localhost:9200 - auth: - bearer: token -``` - ##### With API key ```yaml diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 8d209cc8f0..07258a55cb 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -187,7 +187,7 @@ new `CustomCatalogIndexPage`. # packages/app/src/App.tsx const routes = ( - + - } /> + } /> ``` diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index c0c347ac17..c3af6caf2d 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -57,6 +57,30 @@ if the original location delegates to another location. A common case is, that a location is registered as `bootstrap:bootstrap` which means that it is part of the `app-config.yaml` of a Backstage installation. +### backstage.io/orphan + +This annotation is either absent, or present with the exact _string_ value +`"true"`. It should never be added manually. Instead, the catalog itself injects +the annotation as part of its processing loops, on entities that are found to +have no registered locations or config locations that keep them "active" / +"alive". + +For example, suppose that the user first registers a location URL pointing to a +`Location` kind entity, which in turn refers to two `Component` kind entities in +two other files nearby. The end result is that the catalog contains those three +entities. Now suppose that the user edits the original `Location` entity to only +refer to the first of the `Component` kind entities. This will intentionally +_not_ lead to the other `Component` entity to be removed from the catalog (for +safety reasons). Instead, it gains this orphan marker annotation, to make it +clear that user action is required to completely remove it, if desired. + +```yaml +# Example: +metadata: + annotations: + backstage.io/orphan: 'true' +``` + ### backstage.io/techdocs-ref ```yaml diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 159137c879..6d221bd9ad 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -61,7 +61,7 @@ If you do not prefer (3a) and optionally like to use a service account, you can follow these steps. Create a new Service Account and a key associated with it. In roles of the -service account, use "Storage Admin". +service account, use "Storage Object Admin". If you want to create a custom role, make sure to include both `get` and `create` permissions for both "Objects" and "Buckets". See @@ -143,6 +143,8 @@ permissions to: - `s3:ListBucket` to retrieve bucket metadata - `s3:PutObject` to upload files to the bucket +- `s3:DeleteObject` and `s3:DeleteObjectVersion` to delete stale content during + re-publishing To _read_ TechDocs from the S3 bucket the IAM policy needs to have at a minimum permissions to: @@ -345,6 +347,10 @@ techdocs: accountKey: ${TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY} ``` +In either case, the account or credentials used to access your container and all +TechDocs objects underneath it should have the `Storage Blog Data Owner` role +applied, in order to read, write, and delete objects as needed. + **4. That's it!** Your Backstage app is now ready to use Azure Blob Storage for TechDocs, to store diff --git a/docs/integrations/bitbucket/discovery.md b/docs/integrations/bitbucket/discovery.md index 1538806e39..708acd8db2 100644 --- a/docs/integrations/bitbucket/discovery.md +++ b/docs/integrations/bitbucket/discovery.md @@ -56,13 +56,11 @@ matching repository is processed. repository. ```typescript -const customRepositoryParser: BitbucketRepositoryParser = async function* customRepositoryParser({ - client, - repository, -}) { - // Custom logic for interpret the matching repository. - // See defaultRepositoryParser for an example -}; +const customRepositoryParser: BitbucketRepositoryParser = + async function* customRepositoryParser({ client, repository }) { + // Custom logic for interpret the matching repository. + // See defaultRepositoryParser for an example + }; const processor = BitbucketDiscoveryProcessor.fromConfig(env.config, { parser: customRepositoryParser, diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md index 2b730024fd..ee2628a893 100644 --- a/docs/overview/stability-index.md +++ b/docs/overview/stability-index.md @@ -312,14 +312,6 @@ configuration. Stability: `1` -### `register-component` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/register-component/) - -A frontend plugin that allows the user to register entity locations in the -catalog. - -Stability: `0`. This plugin is likely to be replaced by a generic entity import -plugin instead. - ### `scaffolder` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/scaffolder/) The frontend scaffolder plugin where one can browse templates and initiate diff --git a/docs/plugins/github-apps.md b/docs/plugins/github-apps.md index 805e321093..f9f7590e09 100644 --- a/docs/plugins/github-apps.md +++ b/docs/plugins/github-apps.md @@ -89,6 +89,26 @@ integrations: - $include: example-backstage-app-credentials.yaml ``` +### Limiting the GitHub App installations + +If you want to limit the GitHub app installations visible to backstage you may +optionally include the `allowedInstallationOwners` option. + +```yaml +appId: 1 +allowedInstallationOwners: ['GlobexCorp'] +clientId: client id +clientSecret: client secret +webhookSecret: webhook secret +privateKey: | + -----BEGIN RSA PRIVATE KEY----- + ...Key content... + -----END RSA PRIVATE KEY----- +``` + +This will result in backstage preventing the use of any installation that is not +within the allow list. + ### Permissions for pull requests These are the minimum permissions required for creating a pull request with diff --git a/lerna.json b/lerna.json index dd2dd884eb..322929db1d 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": ["packages/*", "plugins/*"], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.1.1" + "version": "0.1.0" } diff --git a/microsite/data/plugins/xcmetrics.yaml b/microsite/data/plugins/xcmetrics.yaml new file mode 100644 index 0000000000..f2f4f7c013 --- /dev/null +++ b/microsite/data/plugins/xcmetrics.yaml @@ -0,0 +1,9 @@ +--- +title: XCMetrics +author: Spotify +authorUrl: https://github.com/spotify +category: Monitoring +description: Discover valuable insights hiding inside Xcode’s build logs. +documentation: https://xcmetrics.io/ +iconUrl: img/xcmetrics-icon.png +npmPackageName: '@backstage/plugin-xcmetrics' diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 5999524073..c22a7e961d 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -80,7 +80,8 @@ "features/search/search-overview", "features/search/getting-started", "features/search/concepts", - "features/search/architecture" + "features/search/architecture", + "features/search/search-engines" ] }, { diff --git a/microsite/static/img/xcmetrics-icon.png b/microsite/static/img/xcmetrics-icon.png new file mode 100644 index 0000000000..225ee6e147 Binary files /dev/null and b/microsite/static/img/xcmetrics-icon.png differ diff --git a/mkdocs.yml b/mkdocs.yml index 4d984e63e3..bd70104abc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ nav: - Getting Started: 'features/search/getting-started.md' - Concepts: 'features/search/concepts.md' - Search Architecture: 'features/search/architecture.md' + - Search Engines: 'features/search/search-engines.md' - TechDocs: - Overview: 'features/techdocs/README.md' - Getting Started: 'features/techdocs/getting-started.md' diff --git a/package.json b/package.json index ea1cf0c9b4..c9a33f78e6 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,10 @@ "packages": [ "packages/*", "plugins/*" + ], + "nohoist": [ + "**/@storybook/**", + "**/@storybook" ] }, "resolutions": { @@ -55,6 +59,7 @@ "@microsoft/api-extractor-model": "^7.13.3" }, "devDependencies": { + "@types/webpack": "^5.28.0", "@changesets/cli": "^2.14.0", "@octokit/openapi-types": "^2.2.0", "@spotify/prettier-config": "^10.0.0", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index af4e604a1b..14927117ab 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,46 @@ # example-app +## 0.2.40 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.7.7 + - @backstage/plugin-catalog@0.6.10 + - @backstage/core-components@0.3.0 + - @backstage/core-plugin-api@0.1.5 + - @backstage/plugin-api-docs@0.6.5 + - @backstage/plugin-scaffolder@0.10.3 + - @backstage/plugin-search@0.4.6 + - @backstage/plugin-cost-insights@0.11.3 + - @backstage/plugin-kubernetes@0.4.10 + - @backstage/plugin-lighthouse@0.2.22 + - @backstage/plugin-pagerduty@0.3.10 + - @backstage/plugin-techdocs@0.10.3 + - @backstage/search-common@0.1.3 + - @backstage/core-app-api@0.1.7 + - @backstage/integration-react@0.1.6 + - @backstage/plugin-badges@0.2.7 + - @backstage/plugin-catalog-import@0.5.16 + - @backstage/plugin-catalog-react@0.4.1 + - @backstage/plugin-circleci@0.2.21 + - @backstage/plugin-cloudbuild@0.2.21 + - @backstage/plugin-code-coverage@0.1.9 + - @backstage/plugin-explore@0.3.12 + - @backstage/plugin-gcp-projects@0.3.2 + - @backstage/plugin-github-actions@0.4.15 + - @backstage/plugin-graphiql@0.2.14 + - @backstage/plugin-jenkins@0.5.2 + - @backstage/plugin-kafka@0.2.13 + - @backstage/plugin-newrelic@0.3.2 + - @backstage/plugin-org@0.3.19 + - @backstage/plugin-rollbar@0.3.11 + - @backstage/plugin-sentry@0.3.17 + - @backstage/plugin-shortcuts@0.1.6 + - @backstage/plugin-tech-radar@0.4.4 + - @backstage/plugin-todo@0.1.7 + - @backstage/plugin-user-settings@0.3.3 + ## 0.2.39 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index abba8492da..df69e0835a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,45 +1,45 @@ { "name": "example-app", - "version": "0.2.39", + "version": "0.2.40", "private": true, "bundled": true, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/cli": "^0.7.6", - "@backstage/core-app-api": "^0.1.6", - "@backstage/core-components": "^0.2.0", - "@backstage/core-plugin-api": "^0.1.4", - "@backstage/integration-react": "^0.1.5", - "@backstage/plugin-api-docs": "^0.6.4", - "@backstage/plugin-badges": "^0.2.6", - "@backstage/plugin-catalog": "^0.6.9", - "@backstage/plugin-catalog-import": "^0.5.15", - "@backstage/plugin-catalog-react": "^0.4.0", - "@backstage/plugin-circleci": "^0.2.20", - "@backstage/plugin-cloudbuild": "^0.2.20", - "@backstage/plugin-code-coverage": "^0.1.8", - "@backstage/plugin-cost-insights": "^0.11.2", - "@backstage/plugin-explore": "^0.3.11", - "@backstage/plugin-gcp-projects": "^0.3.1", - "@backstage/plugin-github-actions": "^0.4.14", - "@backstage/plugin-graphiql": "^0.2.13", - "@backstage/plugin-jenkins": "^0.5.1", - "@backstage/plugin-kafka": "^0.2.12", - "@backstage/plugin-kubernetes": "^0.4.9", - "@backstage/plugin-lighthouse": "^0.2.21", - "@backstage/plugin-newrelic": "^0.3.1", - "@backstage/plugin-org": "^0.3.18", - "@backstage/plugin-pagerduty": "0.3.9", - "@backstage/plugin-rollbar": "^0.3.10", - "@backstage/plugin-scaffolder": "^0.10.2", - "@backstage/plugin-search": "^0.4.5", - "@backstage/plugin-sentry": "^0.3.16", - "@backstage/plugin-shortcuts": "^0.1.5", - "@backstage/plugin-tech-radar": "^0.4.3", - "@backstage/plugin-techdocs": "^0.10.2", - "@backstage/plugin-todo": "^0.1.6", - "@backstage/plugin-user-settings": "^0.3.2", - "@backstage/search-common": "^0.1.2", + "@backstage/cli": "^0.7.7", + "@backstage/core-app-api": "^0.1.7", + "@backstage/core-components": "^0.3.0", + "@backstage/core-plugin-api": "^0.1.5", + "@backstage/integration-react": "^0.1.6", + "@backstage/plugin-api-docs": "^0.6.5", + "@backstage/plugin-badges": "^0.2.7", + "@backstage/plugin-catalog": "^0.6.10", + "@backstage/plugin-catalog-import": "^0.5.16", + "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/plugin-circleci": "^0.2.21", + "@backstage/plugin-cloudbuild": "^0.2.21", + "@backstage/plugin-code-coverage": "^0.1.9", + "@backstage/plugin-cost-insights": "^0.11.3", + "@backstage/plugin-explore": "^0.3.12", + "@backstage/plugin-gcp-projects": "^0.3.2", + "@backstage/plugin-github-actions": "^0.4.15", + "@backstage/plugin-graphiql": "^0.2.14", + "@backstage/plugin-jenkins": "^0.5.2", + "@backstage/plugin-kafka": "^0.2.13", + "@backstage/plugin-kubernetes": "^0.4.10", + "@backstage/plugin-lighthouse": "^0.2.22", + "@backstage/plugin-newrelic": "^0.3.2", + "@backstage/plugin-org": "^0.3.19", + "@backstage/plugin-pagerduty": "0.3.10", + "@backstage/plugin-rollbar": "^0.3.11", + "@backstage/plugin-scaffolder": "^0.10.3", + "@backstage/plugin-search": "^0.4.6", + "@backstage/plugin-sentry": "^0.3.17", + "@backstage/plugin-shortcuts": "^0.1.6", + "@backstage/plugin-tech-radar": "^0.4.4", + "@backstage/plugin-techdocs": "^0.10.3", + "@backstage/plugin-todo": "^0.1.7", + "@backstage/plugin-user-settings": "^0.3.3", + "@backstage/search-common": "^0.1.3", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -61,6 +61,7 @@ }, "devDependencies": { "@backstage/test-utils": "^0.1.16", + "@rjsf/core": "^3.0.0", "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", @@ -68,7 +69,7 @@ "@types/jest": "^26.0.7", "@types/jquery": "^3.3.34", "@types/node": "^14.14.32", - "@types/react-dom": "^16.9.8", + "@types/react-dom": "*", "@types/zen-observable": "^0.8.0", "cross-env": "^7.0.0", "cypress": "^7.3.0", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index de62971f96..1132368a91 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -41,10 +41,22 @@ import { GcpProjectsPage } from '@backstage/plugin-gcp-projects'; import { GraphiQLPage } from '@backstage/plugin-graphiql'; import { LighthousePage } from '@backstage/plugin-lighthouse'; import { NewRelicPage } from '@backstage/plugin-newrelic'; -import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; +import { + ScaffolderPage, + scaffolderPlugin, + ScaffolderFieldExtensions, + RepoUrlPickerFieldExtension, + OwnerPickerFieldExtension, + EntityPickerFieldExtension, + EntityNamePickerFieldExtension, +} from '@backstage/plugin-scaffolder'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; -import { TechdocsPage } from '@backstage/plugin-techdocs'; +import { + DefaultTechDocsHome, + TechDocsIndexPage, + TechDocsReaderPage, +} from '@backstage/plugin-techdocs'; import { UserSettingsPage } from '@backstage/plugin-user-settings'; import AlarmIcon from '@material-ui/icons/Alarm'; import React from 'react'; @@ -54,6 +66,7 @@ import { apis } from './apis'; import { Root } from './components/Root'; import { entityPage } from './components/catalog/EntityPage'; import { searchPage } from './components/search/SearchPage'; +import { LowerCaseValuePickerFieldExtension } from './components/scaffolder/customScaffolderExtensions'; import { providers } from './identityProviders'; import * as plugins from './plugins'; @@ -98,7 +111,7 @@ const AppRouter = app.getRouter(); const routes = ( - + } /> } /> - } /> - } /> + }> + + + } + /> + }> + + + + + + + + } /> } /> } /> - } /> } /> } /> diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 48141e4f53..7a666a020a 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -82,7 +82,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( {/* Global nav, not org-specific */} - + diff --git a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx new file mode 100644 index 0000000000..5ef4f617a7 --- /dev/null +++ b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx @@ -0,0 +1,34 @@ +/* + * 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 type { FieldValidation } from '@rjsf/core'; +import { + createScaffolderFieldExtension, + TextValuePicker, + scaffolderPlugin, +} from '@backstage/plugin-scaffolder'; + +export const LowerCaseValuePickerFieldExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + name: 'LowerCaseValuePicker', + component: TextValuePicker, + validation: (value: string, validation: FieldValidation) => { + if (value.toLowerCase() !== value) { + validation.addError('Only lowercase values are allowed.'); + } + }, + }), +); diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 582badb685..f6f61133ea 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/backend-common +## 0.8.8 + +### Patch Changes + +- 6aa7c3db7: bump node-tar version to the latest +- Updated dependencies + - @backstage/config@0.1.6 + - @backstage/integration@0.5.9 + - @backstage/config-loader@0.6.6 + ## 0.8.7 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 3f08b42aab..10602fa2fa 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.8.7", + "version": "0.8.8", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -30,10 +30,10 @@ }, "dependencies": { "@backstage/cli-common": "^0.1.2", - "@backstage/config": "^0.1.5", - "@backstage/config-loader": "^0.6.5", + "@backstage/config": "^0.1.6", + "@backstage/config-loader": "^0.6.6", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.8", + "@backstage/integration": "^0.5.9", "@google-cloud/storage": "^5.8.0", "@octokit/rest": "^18.5.3", "@types/cors": "^2.8.6", @@ -76,7 +76,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.7.5", + "@backstage/cli": "^0.7.7", "@backstage/test-utils": "^0.1.15", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", diff --git a/packages/backend-common/src/cache/CacheManager.test.ts b/packages/backend-common/src/cache/CacheManager.test.ts index b55b63f469..fb73687f54 100644 --- a/packages/backend-common/src/cache/CacheManager.test.ts +++ b/packages/backend-common/src/cache/CacheManager.test.ts @@ -104,7 +104,7 @@ describe('CacheManager', () => { manager.forPlugin(plugin2Id).getClient({ defaultTtl: expectedTtl }); const client = DefaultCacheClient as jest.Mock; - const cache = (Keyv as unknown) as jest.Mock; + const cache = Keyv as unknown as jest.Mock; expect(cache).toHaveBeenCalledTimes(2); expect(client).toHaveBeenCalledTimes(2); @@ -124,7 +124,7 @@ describe('CacheManager', () => { const expectedNamespace = 'test-plugin'; manager.forPlugin(expectedNamespace).getClient(); - const cache = (Keyv as unknown) as jest.Mock; + const cache = Keyv as unknown as jest.Mock; const mockCalls = cache.mock.calls.splice(-1); const callArgs = mockCalls[0]; expect(callArgs[0].store).toBeInstanceOf(NoStore); @@ -138,7 +138,7 @@ describe('CacheManager', () => { .forPlugin(expectedNamespace) .getClient({ defaultTtl: expectedTtl }); - const cache = (Keyv as unknown) as jest.Mock; + const cache = Keyv as unknown as jest.Mock; const mockCalls = cache.mock.calls.splice(-1); const callArgs = mockCalls[0]; expect(callArgs[0]).toMatchObject({ @@ -162,7 +162,7 @@ describe('CacheManager', () => { const expectedTtl = 3600; manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - const cache = (Keyv as unknown) as jest.Mock; + const cache = Keyv as unknown as jest.Mock; const mockCacheCalls = cache.mock.calls.splice(-1); expect(mockCacheCalls[0][0]).toMatchObject({ ttl: expectedTtl, diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index f3a3f218b0..2d60104bcb 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -110,9 +110,7 @@ export class DatabaseManager { * @returns Object with client type returned as `client` and boolean representing whether * or not the client was overridden as `overridden` */ - private getClientType( - pluginId: string, - ): { + private getClientType(pluginId: string): { client: string; overridden: boolean; } { diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts index f2f2298559..94dce508c7 100644 --- a/packages/backend-common/src/database/connectors/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -149,7 +149,7 @@ export async function ensureMysqlDatabaseExists( ) { const admin = createMysqlDatabaseClient(dbConfig, { connection: { - database: (null as unknown) as string, + database: null as unknown as string, }, }); diff --git a/packages/backend-common/src/reading/AzureUrlReader.test.ts b/packages/backend-common/src/reading/AzureUrlReader.test.ts index 2b7c21d4ab..c7c8853929 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.test.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.test.ts @@ -78,21 +78,17 @@ describe('AzureUrlReader', () => { it.each([ { - url: - 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster', + url: 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster', config: createConfig(), response: expect.objectContaining({ - url: - 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master', + url: 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master', }), }, { - url: - 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml', + url: 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml', config: createConfig(), response: expect.objectContaining({ - url: - 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml', + url: 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml', }), }, { diff --git a/packages/backend-common/src/reading/AzureUrlReader.ts b/packages/backend-common/src/reading/AzureUrlReader.ts index bc22854d4e..d2c056508d 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.ts @@ -129,7 +129,7 @@ export class AzureUrlReader implements UrlReader { } return await this.deps.treeResponseFactory.fromZipArchive({ - stream: (archiveAzureResponse.body as unknown) as Readable, + stream: archiveAzureResponse.body as unknown as Readable, etag: commitSha, filter: options?.filter, }); diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.ts b/packages/backend-common/src/reading/BitbucketUrlReader.ts index 2849bc3cd9..2a979a464f 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.ts @@ -60,13 +60,8 @@ export class BitbucketUrlReader implements UrlReader { private readonly integration: BitbucketIntegration, private readonly deps: { treeResponseFactory: ReadTreeResponseFactory }, ) { - const { - host, - apiBaseUrl, - token, - username, - appPassword, - } = integration.config; + const { host, apiBaseUrl, token, username, appPassword } = + integration.config; if (!apiBaseUrl) { throw new Error( @@ -138,7 +133,7 @@ export class BitbucketUrlReader implements UrlReader { } return await this.deps.treeResponseFactory.fromTarArchive({ - stream: (archiveBitbucketResponse.body as unknown) as Readable, + stream: archiveBitbucketResponse.body as unknown as Readable, subpath: filepath, etag: lastCommitShortHash, filter: options?.filter, diff --git a/packages/backend-common/src/reading/GithubUrlReader.test.ts b/packages/backend-common/src/reading/GithubUrlReader.test.ts index c8002cd3ce..f9717a862a 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.test.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.test.ts @@ -41,9 +41,9 @@ const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config: new ConfigReader({}), }); -const mockCredentialsProvider = ({ +const mockCredentialsProvider = { getCredentials: jest.fn().mockResolvedValue({ headers: {} }), -} as unknown) as GithubCredentialsProvider; +} as unknown as GithubCredentialsProvider; const githubProcessor = new GithubUrlReader( new GitHubIntegration( diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index bccddc6838..930c544287 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -39,10 +39,14 @@ import { ReadUrlResponse, } from './types'; -export type GhRepoResponse = RestEndpointMethodTypes['repos']['get']['response']['data']; -export type GhBranchResponse = RestEndpointMethodTypes['repos']['getBranch']['response']['data']; -export type GhTreeResponse = RestEndpointMethodTypes['git']['getTree']['response']['data']; -export type GhBlobResponse = RestEndpointMethodTypes['git']['getBlob']['response']['data']; +export type GhRepoResponse = + RestEndpointMethodTypes['repos']['get']['response']['data']; +export type GhBranchResponse = + RestEndpointMethodTypes['repos']['getBranch']['response']['data']; +export type GhTreeResponse = + RestEndpointMethodTypes['git']['getTree']['response']['data']; +export type GhBlobResponse = + RestEndpointMethodTypes['git']['getBlob']['response']['data']; /** * A processor that adds the ability to read files from GitHub v3 APIs, such as @@ -195,7 +199,7 @@ export class GithubUrlReader implements UrlReader { return await this.deps.treeResponseFactory.fromTarArchive({ // TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want // to stick to using that in exclusively backend code. - stream: (archive.body as unknown) as Readable, + stream: archive.body as unknown as Readable, subpath, etag: sha, filter: options?.filter, @@ -258,9 +262,7 @@ export class GithubUrlReader implements UrlReader { })); } - private async getRepoDetails( - url: string, - ): Promise<{ + private async getRepoDetails(url: string): Promise<{ repo: GhRepoResponse; branch: GhBranchResponse; }> { diff --git a/packages/backend-common/src/reading/GitlabUrlReader.test.ts b/packages/backend-common/src/reading/GitlabUrlReader.test.ts index 945444d8ed..51b211e698 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.test.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.test.ts @@ -108,36 +108,30 @@ describe('GitlabUrlReader', () => { it.each([ // Project URLs { - url: - 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', config: createConfig(), response: expect.objectContaining({ - url: - 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', + url: 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', headers: expect.objectContaining({ 'private-token': '', }), }), }, { - url: - 'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + url: 'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', config: createConfig('0123456789'), response: expect.objectContaining({ - url: - 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', + url: 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', headers: expect.objectContaining({ 'private-token': '0123456789', }), }), }, { - url: - 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/to/file.yaml', // Repo not in subgroup + url: 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/to/file.yaml', // Repo not in subgroup config: createConfig(), response: expect.objectContaining({ - url: - 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', + url: 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', }), }, diff --git a/packages/backend-common/src/reading/GitlabUrlReader.ts b/packages/backend-common/src/reading/GitlabUrlReader.ts index 6c272471e2..27c31c7673 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.ts @@ -170,7 +170,7 @@ export class GitlabUrlReader implements UrlReader { } return await this.deps.treeResponseFactory.fromTarArchive({ - stream: (archiveGitLabResponse.body as unknown) as Readable, + stream: archiveGitLabResponse.body as unknown as Readable, subpath: filepath, etag: commitSha, filter: options?.filter, diff --git a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts index 4a166cb517..bbe94a0fb4 100644 --- a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts +++ b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts @@ -52,7 +52,11 @@ export class UrlReaderPredicateMux implements UrlReader { } } - throw new NotAllowedError(`Reading from '${url}' is not allowed`); + throw new NotAllowedError( + `Reading from '${url}' is not allowed. ` + + `You may need to configure an integration for the target host, or add it ` + + `to the configured list of allowed hosts at 'backend.reading.allow'`, + ); } async readUrl( diff --git a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts index a81add58f7..173f6a886a 100644 --- a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts @@ -28,7 +28,7 @@ import { import { stripFirstDirectoryFromPath } from './util'; // Tar types for `Parse` is not a proper constructor, but it should be -const TarParseStream = (Parse as unknown) as { new (): ParseStream }; +const TarParseStream = Parse as unknown as { new (): ParseStream }; const pipeline = promisify(pipelineCb); diff --git a/packages/backend-common/src/scm/git.test.ts b/packages/backend-common/src/scm/git.test.ts index 0b70edc45d..abbafc375d 100644 --- a/packages/backend-common/src/scm/git.test.ts +++ b/packages/backend-common/src/scm/git.test.ts @@ -123,9 +123,9 @@ describe('Git', () => { await git.clone({ url, dir }); - const { onAuth } = ((isomorphic.clone as unknown) as jest.Mock< - typeof isomorphic['clone'] - >).mock.calls[0][0]!; + const { onAuth } = ( + isomorphic.clone as unknown as jest.Mock + ).mock.calls[0][0]!; expect(onAuth()).toEqual(auth); }); @@ -190,9 +190,9 @@ describe('Git', () => { await git.fetch({ remote, dir }); - const { onAuth } = ((isomorphic.fetch as unknown) as jest.Mock< - typeof isomorphic['fetch'] - >).mock.calls[0][0]!; + const { onAuth } = ( + isomorphic.fetch as unknown as jest.Mock + ).mock.calls[0][0]!; expect(onAuth()).toEqual(auth); }); @@ -279,9 +279,9 @@ describe('Git', () => { await git.push({ remote, dir }); - const { onAuth } = ((isomorphic.push as unknown) as jest.Mock< - typeof isomorphic['push'] - >).mock.calls[0][0]!; + const { onAuth } = ( + isomorphic.push as unknown as jest.Mock + ).mock.calls[0][0]!; expect(onAuth()).toEqual(auth); }); diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 7a3ebfed70..ed16c9c9a7 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -154,14 +154,8 @@ export class ServiceBuilderImpl implements ServiceBuilder { async start(): Promise { const app = express(); - const { - port, - host, - logger, - corsOptions, - httpsSettings, - helmetOptions, - } = this.getOptions(); + const { port, host, logger, corsOptions, httpsSettings, helmetOptions } = + this.getOptions(); app.use(helmet(helmetOptions)); if (corsOptions) { diff --git a/packages/backend-common/src/util/DockerContainerRunner.ts b/packages/backend-common/src/util/DockerContainerRunner.ts index 8dbc5335c9..33a4874151 100644 --- a/packages/backend-common/src/util/DockerContainerRunner.ts +++ b/packages/backend-common/src/util/DockerContainerRunner.ts @@ -92,18 +92,17 @@ export class DockerContainerRunner implements ContainerRunner { Env.push(`${key}=${value}`); } - const [ - { Error: error, StatusCode: statusCode }, - ] = await this.dockerClient.run(imageName, args, logStream, { - Volumes, - HostConfig: { - Binds, - }, - ...(workingDir ? { WorkingDir: workingDir } : {}), - Entrypoint: command, - Env, - ...userOptions, - } as Docker.ContainerCreateOptions); + const [{ Error: error, StatusCode: statusCode }] = + await this.dockerClient.run(imageName, args, logStream, { + Volumes, + HostConfig: { + Binds, + }, + ...(workingDir ? { WorkingDir: workingDir } : {}), + Entrypoint: command, + Env, + ...userOptions, + } as Docker.ContainerCreateOptions); if (error) { throw new Error( diff --git a/packages/backend-test-utils/src/database/TestDatabases.test.ts b/packages/backend-test-utils/src/database/TestDatabases.test.ts index 62612f3fcc..000e047c4d 100644 --- a/packages/backend-test-utils/src/database/TestDatabases.test.ts +++ b/packages/backend-test-utils/src/database/TestDatabases.test.ts @@ -71,7 +71,7 @@ describe('TestDatabases', () => { await input.insert({ x: 'y' }).into('a'); // Look for the mark - const database = 'backstage_plugin_db0'; + const database = input.client.config.connection.database; const output = knexFactory({ client: 'pg', connection: { host, port, user, password, database }, @@ -105,7 +105,7 @@ describe('TestDatabases', () => { await input.insert({ x: 'y' }).into('a'); // Look for the mark - const database = 'backstage_plugin_db0'; + const database = input.client.config.connection.database; const output = knexFactory({ client: 'pg', connection: { host, port, user, password, database }, @@ -139,7 +139,7 @@ describe('TestDatabases', () => { await input.insert({ x: 'y' }).into('a'); // Look for the mark - const database = 'backstage_plugin_db0'; + const database = input.client.config.connection.database; const output = knexFactory({ client: 'mysql2', connection: { host, port, user, password, database }, diff --git a/packages/backend-test-utils/src/database/TestDatabases.ts b/packages/backend-test-utils/src/database/TestDatabases.ts index c35202354d..329aec5e8a 100644 --- a/packages/backend-test-utils/src/database/TestDatabases.ts +++ b/packages/backend-test-utils/src/database/TestDatabases.ts @@ -16,6 +16,7 @@ import { DatabaseManager } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import { randomBytes } from 'crypto'; import { Knex } from 'knex'; import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; import { startMysqlContainer } from './startMysqlContainer'; @@ -34,7 +35,6 @@ import { export class TestDatabases { private readonly instanceById: Map; private readonly supportedIds: TestDatabaseId[]; - private lastDatabaseIndex: number; /** * Creates an empty `TestDatabases` instance, and sets up Jest to clean up @@ -99,7 +99,6 @@ export class TestDatabases { private constructor(supportedIds: TestDatabaseId[]) { this.instanceById = new Map(); this.supportedIds = supportedIds; - this.lastDatabaseIndex = 0; } supports(id: TestDatabaseId): boolean { @@ -142,7 +141,7 @@ export class TestDatabases { // Ensure that a unique logical database is created in the instance const connection = await instance.databaseManager - .forPlugin(String(`db${this.lastDatabaseIndex++}`)) + .forPlugin(`db${randomBytes(16).toString('hex')}`) .getClient(); instance.connections.push(connection); diff --git a/packages/backend-test-utils/src/database/types.ts b/packages/backend-test-utils/src/database/types.ts index b5516a19c0..514a3a85e8 100644 --- a/packages/backend-test-utils/src/database/types.ts +++ b/packages/backend-test-utils/src/database/types.ts @@ -38,33 +38,31 @@ export type Instance = { databaseManager: DatabaseManager; connections: Array; }; -export const allDatabases: Record< - TestDatabaseId, - TestDatabaseProperties -> = Object.freeze({ - POSTGRES_13: { - name: 'Postgres 13.x', - driver: 'pg', - dockerImageName: 'postgres:13', - connectionStringEnvironmentVariableName: - 'BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING', - }, - POSTGRES_9: { - name: 'Postgres 9.x', - driver: 'pg', - dockerImageName: 'postgres:9', - connectionStringEnvironmentVariableName: - 'BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING', - }, - MYSQL_8: { - name: 'MySQL 8.x', - driver: 'mysql2', - dockerImageName: 'mysql:8', - connectionStringEnvironmentVariableName: - 'BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING', - }, - SQLITE_3: { - name: 'SQLite 3.x', - driver: 'sqlite3', - }, -}); +export const allDatabases: Record = + Object.freeze({ + POSTGRES_13: { + name: 'Postgres 13.x', + driver: 'pg', + dockerImageName: 'postgres:13', + connectionStringEnvironmentVariableName: + 'BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING', + }, + POSTGRES_9: { + name: 'Postgres 9.x', + driver: 'pg', + dockerImageName: 'postgres:9', + connectionStringEnvironmentVariableName: + 'BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING', + }, + MYSQL_8: { + name: 'MySQL 8.x', + driver: 'mysql2', + dockerImageName: 'mysql:8', + connectionStringEnvironmentVariableName: + 'BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING', + }, + SQLITE_3: { + name: 'SQLite 3.x', + driver: 'sqlite3', + }, + }); diff --git a/packages/backend/package.json b/packages/backend/package.json index 5d4ff60d68..33221bde01 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -48,6 +48,7 @@ "@backstage/plugin-search-backend": "^0.2.3", "@backstage/plugin-search-backend-node": "^0.4.0", "@backstage/plugin-search-backend-module-elasticsearch": "^0.0.1", + "@backstage/plugin-search-backend-module-pg": "^0.1.0", "@backstage/plugin-techdocs-backend": "^0.9.0", "@backstage/plugin-todo-backend": "^0.1.8", "@gitbeaker/node": "^30.2.0", diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index 78ce09ee07..3470110408 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -13,29 +13,55 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useHotCleanup } from '@backstage/backend-common'; +import { + PluginDatabaseManager, + useHotCleanup, +} from '@backstage/backend-common'; +import { Config } from '@backstage/config'; +import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; import { createRouter } from '@backstage/plugin-search-backend'; +import { ElasticSearchSearchEngine } from '@backstage/plugin-search-backend-module-elasticsearch'; +import { PgSearchEngine } from '@backstage/plugin-search-backend-module-pg'; import { IndexBuilder, LunrSearchEngine, + SearchEngine, } from '@backstage/plugin-search-backend-node'; -import { PluginEnvironment } from '../types'; -import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; -import { ElasticSearchSearchEngine } from '@backstage/plugin-search-backend-module-elasticsearch'; +import { Logger } from 'winston'; +import { PluginEnvironment } from '../types'; + +async function createSearchEngine({ + logger, + database, + config, +}: { + logger: Logger; + database: PluginDatabaseManager; + config: Config; +}): Promise { + if (config.has('search.elasticsearch')) { + return await ElasticSearchSearchEngine.fromConfig({ + logger, + config, + }); + } + + if (await PgSearchEngine.supported(database)) { + return await PgSearchEngine.from({ database }); + } + + return new LunrSearchEngine({ logger }); +} export default async function createPlugin({ logger, discovery, config, + database, }: PluginEnvironment) { // Initialize a connection to a search engine. - const searchEngine = config.has('search.elasticsearch') - ? await ElasticSearchSearchEngine.fromConfig({ - logger, - config, - }) - : new LunrSearchEngine({ logger }); + const searchEngine = await createSearchEngine({ config, logger, database }); const indexBuilder = new IndexBuilder({ logger, searchEngine }); // Collators are responsible for gathering documents known to plugins. This diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 437a0be02b..13041d9e32 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -190,7 +190,7 @@ export type EntityEnvelope = { // // @public export function entityEnvelopeSchemaValidator< - T extends EntityEnvelope = EntityEnvelope + T extends EntityEnvelope = EntityEnvelope, >(schema?: unknown): (data: unknown) => T; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen @@ -333,9 +333,7 @@ export function getEntityName(entity: Entity): EntityName; // Warning: (ae-missing-release-tag) "getEntitySourceLocation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export function getEntitySourceLocation( - entity: Entity, -): { +export function getEntitySourceLocation(entity: Entity): { type: string; target: string; }; @@ -542,9 +540,7 @@ export function parseEntityRef( // Warning: (ae-missing-release-tag) "parseLocationReference" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export function parseLocationReference( - ref: string, -): { +export function parseLocationReference(ref: string): { type: string; target: string; }; diff --git a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts index 21539aaba4..c263689446 100644 --- a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts @@ -58,7 +58,7 @@ describe('SchemaValidEntityPolicy', () => { // it('rejects wrong root type', async () => { - await expect(policy.enforce((7 as unknown) as Entity)).rejects.toThrow( + await expect(policy.enforce(7 as unknown as Entity)).rejects.toThrow( /object/, ); }); diff --git a/packages/catalog-model/src/entity/ref.ts b/packages/catalog-model/src/entity/ref.ts index 91d8547c6b..e2d9c5b203 100644 --- a/packages/catalog-model/src/entity/ref.ts +++ b/packages/catalog-model/src/entity/ref.ts @@ -18,9 +18,7 @@ import { EntityName, EntityRef } from '../types'; import { ENTITY_DEFAULT_NAMESPACE } from './constants'; import { Entity } from './Entity'; -function parseRefString( - ref: string, -): { +function parseRefString(ref: string): { kind?: string; namespace?: string; name: string; diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts index 737037e6b2..2bcd9c7040 100644 --- a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts @@ -30,6 +30,5 @@ export interface ApiEntityV1alpha1 extends Entity { }; } -export const apiEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator( - schema, -); +export const apiEntityV1alpha1Validator = + ajvCompiledJsonSchemaValidator(schema); diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 4e08d4cce9..5312d87a35 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -33,6 +33,5 @@ export interface ComponentEntityV1alpha1 extends Entity { }; } -export const componentEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator( - schema, -); +export const componentEntityV1alpha1Validator = + ajvCompiledJsonSchemaValidator(schema); diff --git a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts index c2f39de321..c832f35ee0 100644 --- a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts @@ -26,6 +26,5 @@ export interface DomainEntityV1alpha1 extends Entity { }; } -export const domainEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator( - schema, -); +export const domainEntityV1alpha1Validator = + ajvCompiledJsonSchemaValidator(schema); diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index 28f1503223..263ba311ad 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -34,6 +34,5 @@ export interface GroupEntityV1alpha1 extends Entity { }; } -export const groupEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator( - schema, -); +export const groupEntityV1alpha1Validator = + ajvCompiledJsonSchemaValidator(schema); diff --git a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts index 4a37340bee..44f04b2c39 100644 --- a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts @@ -28,6 +28,5 @@ export interface LocationEntityV1alpha1 extends Entity { }; } -export const locationEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator( - schema, -); +export const locationEntityV1alpha1Validator = + ajvCompiledJsonSchemaValidator(schema); diff --git a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts index c8f96b87e3..211164f4c1 100644 --- a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts @@ -29,6 +29,5 @@ export interface ResourceEntityV1alpha1 extends Entity { }; } -export const resourceEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator( - schema, -); +export const resourceEntityV1alpha1Validator = + ajvCompiledJsonSchemaValidator(schema); diff --git a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts index 7c719566ad..129e3fea50 100644 --- a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts @@ -27,6 +27,5 @@ export interface SystemEntityV1alpha1 extends Entity { }; } -export const systemEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator( - schema, -); +export const systemEntityV1alpha1Validator = + ajvCompiledJsonSchemaValidator(schema); diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts b/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts index 2710fe1275..577e5ec9e2 100644 --- a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts +++ b/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts @@ -40,6 +40,5 @@ export interface TemplateEntityV1beta2 extends Entity { }; } -export const templateEntityV1beta2Validator = ajvCompiledJsonSchemaValidator( - schema, -); +export const templateEntityV1beta2Validator = + ajvCompiledJsonSchemaValidator(schema); diff --git a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts index 267a9f07fd..65ebdb3262 100644 --- a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts @@ -31,6 +31,5 @@ export interface UserEntityV1alpha1 extends Entity { }; } -export const userEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator( - schema, -); +export const userEntityV1alpha1Validator = + ajvCompiledJsonSchemaValidator(schema); diff --git a/packages/catalog-model/src/location/helpers.ts b/packages/catalog-model/src/location/helpers.ts index 431ee71218..91209e7a2a 100644 --- a/packages/catalog-model/src/location/helpers.ts +++ b/packages/catalog-model/src/location/helpers.ts @@ -26,9 +26,10 @@ import { LOCATION_ANNOTATION, SOURCE_LOCATION_ANNOTATION } from './annotation'; * @param ref A string-form location reference, e.g. 'url:https://host' * @returns A location reference, e.g. { type: 'url', target: 'https://host' } */ -export function parseLocationReference( - ref: string, -): { type: string; target: string } { +export function parseLocationReference(ref: string): { + type: string; + target: string; +} { if (typeof ref !== 'string') { throw new TypeError( `Unable to parse location reference '${ref}', unexpected argument ${typeof ref}`, @@ -91,9 +92,10 @@ export function stringifyLocationReference(ref: { * using the UrlReader from @backstage/backend-common. If it is not of type 'url', the caller * needs to have explicit handling of each location type or signal that it is not supported. */ -export function getEntitySourceLocation( - entity: Entity, -): { type: string; target: string } { +export function getEntitySourceLocation(entity: Entity): { + type: string; + target: string; +} { const locationRef = entity.metadata?.annotations?.[SOURCE_LOCATION_ANNOTATION] ?? entity.metadata?.annotations?.[LOCATION_ANNOTATION]; diff --git a/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.ts b/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.ts index 2fe74ea2a4..aa865bcf1c 100644 --- a/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.ts +++ b/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.ts @@ -41,7 +41,7 @@ import { compileAjvSchema, throwAjvError } from './ajv'; * @see https://github.com/backstage/backstage/tree/master/packages/catalog-model/src/schema */ export function entityEnvelopeSchemaValidator< - T extends EntityEnvelope = EntityEnvelope + T extends EntityEnvelope = EntityEnvelope, >(schema?: unknown): (data: unknown) => T { const validate = compileAjvSchema( schema ? (schema as Schema) : entityEnvelopeSchema, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 226712d209..dc4512adee 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/cli +## 0.7.7 + +### Patch Changes + +- 6aa7c3db7: bump node-tar version to the latest +- e9d3983ee: Keep track of filtered configuration values when running frontend in development mode. +- Updated dependencies + - @backstage/config@0.1.6 + - @backstage/config-loader@0.6.6 + ## 0.7.6 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 254a60c9ca..c0e6d987f1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.7.6", + "version": "0.7.7", "private": false, "publishConfig": { "access": "public" @@ -31,8 +31,8 @@ "@babel/core": "^7.4.4", "@babel/plugin-transform-modules-commonjs": "^7.4.4", "@backstage/cli-common": "^0.1.2", - "@backstage/config": "^0.1.5", - "@backstage/config-loader": "^0.6.5", + "@backstage/config": "^0.1.6", + "@backstage/config-loader": "^0.6.6", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^4.0.0", "@lerna/project": "^4.0.0", @@ -50,9 +50,7 @@ "@svgr/plugin-svgo": "5.4.x", "@svgr/rollup": "5.5.x", "@svgr/webpack": "5.5.x", - "@types/start-server-webpack-plugin": "^2.2.0", "@types/webpack-env": "^1.15.2", - "@types/webpack-node-externals": "^2.5.0", "@typescript-eslint/eslint-plugin": "^v4.28.3", "@typescript-eslint/parser": "^v4.28.3", "@yarnpkg/lockfile": "^1.1.0", @@ -76,18 +74,19 @@ "eslint-plugin-react-hooks": "^4.0.0", "express": "^4.17.1", "file-loader": "^6.2.0", - "fork-ts-checker-webpack-plugin": "^6.2.9", + "fork-ts-checker-webpack-plugin": "^4.0.5", "fs-extra": "9.1.0", "handlebars": "^4.7.3", - "html-webpack-plugin": "^4.3.0", + "html-webpack-plugin": "^5.3.1", "inquirer": "^7.0.4", "jest": "^26.0.1", "jest-css-modules": "^2.1.0", "json-schema": "^0.3.0", "lodash": "^4.17.19", - "mini-css-extract-plugin": "^0.9.0", + "mini-css-extract-plugin": "^1.4.1", "ora": "^5.3.0", "postcss": "^8.1.0", + "process": "^0.11.10", "raw-loader": "^4.0.1", "react": "^16.0.0", "react-dev-utils": "^11.0.4", @@ -100,17 +99,18 @@ "rollup-plugin-peer-deps-external": "^2.2.2", "rollup-plugin-postcss": "^4.0.0", "rollup-pluginutils": "^2.8.2", + "run-script-webpack-plugin": "^0.0.11", "semver": "^7.3.2", - "start-server-webpack-plugin": "^2.2.5", "style-loader": "^1.2.1", "sucrase": "^3.18.2", "tar": "^6.1.2", - "terser-webpack-plugin": "^1.4.3", + "terser-webpack-plugin": "^5.1.3", "ts-loader": "^8.0.17", "typescript": "^4.0.3", "url-loader": "^4.1.0", - "webpack": "^4.41.6", - "webpack-dev-server": "3.11.0", + "util": "^0.12.3", + "webpack": "^5.48.0", + "webpack-dev-server": "4.0.0-rc.0", "webpack-node-externals": "^3.0.0", "yaml": "^1.10.0", "yaml-jest": "^1.0.5", @@ -118,36 +118,37 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.8.7", - "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.2.0", - "@backstage/core-plugin-api": "^0.1.4", - "@backstage/core-app-api": "^0.1.6", - "@backstage/dev-utils": "^0.2.4", + "@backstage/backend-common": "^0.8.8", + "@backstage/config": "^0.1.6", + "@backstage/core-components": "^0.3.0", + "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-app-api": "^0.1.7", + "@backstage/dev-utils": "^0.2.5", "@backstage/test-utils": "^0.1.16", "@backstage/theme": "^0.2.9", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", "@types/fs-extra": "^9.0.1", - "@types/html-webpack-plugin": "^3.2.2", "@types/http-proxy": "^1.17.4", "@types/inquirer": "^7.3.1", - "@types/mini-css-extract-plugin": "^1.2.2", "@types/mock-fs": "^4.13.0", "@types/node": "^14.14.32", - "@types/react-dev-utils": "^9.0.4", "@types/recursive-readdir": "^2.2.0", "@types/rollup-plugin-peer-deps-external": "^2.2.0", "@types/rollup-plugin-postcss": "^2.0.0", "@types/tar": "^4.0.3", - "@types/webpack": "^4.41.7", - "@types/webpack-dev-server": "^3.11.0", + "@types/terser-webpack-plugin": "^5.0.4", + "@types/webpack": "^5.28.0", + "@types/webpack-dev-server": "^3.11.5", "@types/yarnpkg__lockfile": "^1.1.4", "del": "^6.0.0", "mock-fs": "^4.13.0", "nodemon": "^2.0.2", "ts-node": "^10.0.0" }, + "resolutions": { + "@types/webpack-dev-server/@types/webpack": "^5.28.0" + }, "files": [ "asset-types", "templates", diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index 68d0766217..c542da6dfb 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -55,11 +55,9 @@ export default async (cmd: Command) => { lockfile.replaceVersions(result.newVersions); await lockfile.save(); } else { - const [ - newVersionsForbidden, - newVersionsAllowed, - ] = partition(result.newVersions, ({ name }) => - forbiddenDuplicatesFilter(name), + const [newVersionsForbidden, newVersionsAllowed] = partition( + result.newVersions, + ({ name }) => forbiddenDuplicatesFilter(name), ); if (newVersionsForbidden.length && !fix) { success = false; diff --git a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts index aa6a6b4e2e..91615eddee 100644 --- a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts +++ b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts @@ -15,14 +15,14 @@ */ import { resolve as resolvePath } from 'path'; -import { ResolvePlugin } from 'webpack'; +import { WebpackPluginInstance } from 'webpack'; import { isChildPath } from '@backstage/cli-common'; import { LernaPackage } from './types'; // Enables proper resolution of packages when linking in external packages. // Without this the packages would depend on dependencies in the node_modules // of the external packages themselves, leading to module duplication -export class LinkedPackageResolvePlugin implements ResolvePlugin { +export class LinkedPackageResolvePlugin implements WebpackPluginInstance { constructor( private readonly targetModules: string, private readonly packages: LernaPackage[], diff --git a/packages/cli/src/lib/bundler/backend.ts b/packages/cli/src/lib/bundler/backend.ts index bddeac0de3..567fc3476a 100644 --- a/packages/cli/src/lib/bundler/backend.ts +++ b/packages/cli/src/lib/bundler/backend.ts @@ -26,23 +26,17 @@ export async function serveBackend(options: BackendServeOptions) { isDev: true, }); - const compiler = webpack(config); - - const watcher = compiler.watch( - { - poll: true, - }, - (err: Error) => { - if (err) { - console.error(err); - } else console.log('Build succeeded'); - }, - ); + const compiler = webpack(config, (err: Error | undefined) => { + if (err) { + console.log('here'); + console.error(err); + } else console.log('Build succeeded'); + }); const waitForExit = async () => { for (const signal of ['SIGINT', 'SIGTERM'] as const) { process.on(signal, () => { - watcher.close(() => console.log('Stopped watcher')); + compiler.close(() => console.log('Stopped watcher')); // exit instead of resolve. The process is shutting down and resolving a promise here logs an error process.exit(); }); diff --git a/packages/cli/src/lib/bundler/bundle.ts b/packages/cli/src/lib/bundler/bundle.ts index 17a7398b2a..082b1247ab 100644 --- a/packages/cli/src/lib/bundler/bundle.ts +++ b/packages/cli/src/lib/bundler/bundle.ts @@ -69,6 +69,10 @@ export async function buildBundle(options: BuildOptions) { throw new Error(`Failed to compile.\n${error.message || error}`); }); + if (!stats) { + throw new Error('No stats returned'); + } + if (statsJsonEnabled) { // No @types/bfj await require('bfj').write( @@ -87,25 +91,32 @@ export async function buildBundle(options: BuildOptions) { } async function build(compiler: webpack.Compiler, isCi: boolean) { - const stats = await new Promise((resolve, reject) => { - compiler.run((err, buildStats) => { - if (err) { - if (err.message) { - const { errors } = formatWebpackMessages({ - errors: [err.message], - warnings: new Array(), - } as webpack.Stats.ToJsonOutput); + const stats = await new Promise( + (resolve, reject) => { + compiler.run((err, buildStats) => { + if (err) { + if (err.message) { + const { errors } = formatWebpackMessages({ + errors: [err.message], + warnings: new Array(), + _showErrors: true, + _showWarnings: true, + }); - throw new Error(errors[0]); + throw new Error(errors[0]); + } else { + reject(err); + } } else { - reject(err); + resolve(buildStats); } - } else { - resolve(buildStats); - } - }); - }); + }); + }, + ); + if (!stats) { + throw new Error('No stats provided'); + } const { errors, warnings } = formatWebpackMessages( stats.toJson({ all: false, warnings: true, errors: true }), ); diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 75e94af716..f749c70ed2 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -19,8 +19,8 @@ import { resolve as resolvePath } from 'path'; import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin'; -import StartServerPlugin from 'start-server-webpack-plugin'; -import webpack from 'webpack'; +import { RunScriptWebpackPlugin } from 'run-script-webpack-plugin'; +import webpack, { ProvidePlugin } from 'webpack'; import nodeExternals from 'webpack-node-externals'; import { isChildPath } from '@backstage/cli-common'; import { optimization } from './optimization'; @@ -98,10 +98,9 @@ export async function createConfig( if (checksEnabled) { plugins.push( new ForkTsCheckerWebpackPlugin({ - typescript: { - configFile: paths.targetTsConfig, - }, - eslint: { + typescript: paths.targetTsConfig, + eslint: true, + eslintOptions: { files: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'], options: { parserOptions: { @@ -114,6 +113,15 @@ export async function createConfig( ); } + // TODO(blam): process is no longer auto polyfilled by webpack in v5. + // we use the provide plugin to provide this polyfill, but lets look + // to remove this eventually! + plugins.push( + new ProvidePlugin({ + process: 'process/browser', + }), + ); + plugins.push( new webpack.EnvironmentPlugin({ APP_CONFIG: options.frontendAppConfigs, @@ -156,27 +164,33 @@ export async function createConfig( return { mode: isDev ? 'development' : 'production', profile: false, - node: { - module: 'empty', - dgram: 'empty', - dns: 'mock', - fs: 'empty', - http2: 'empty', - net: 'empty', - tls: 'empty', - child_process: 'empty', - }, optimization: optimization(options), bail: false, performance: { hints: false, // we check the gzip size instead }, - devtool: isDev ? 'cheap-module-eval-source-map' : 'source-map', + devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map', context: paths.targetPath, entry: [require.resolve('react-hot-loader/patch'), paths.targetEntry], resolve: { extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx'], mainFields: ['browser', 'module', 'main'], + fallback: { + module: false, + dgram: false, + dns: false, + fs: false, + http2: false, + net: false, + tls: false, + child_process: false, + + /* new ignores */ + path: false, + https: false, + http: false, + util: require.resolve('util/'), + }, plugins: [ new LinkedPackageResolvePlugin(paths.rootNodeModules, externalPkgs), new ModuleScopePlugin( @@ -200,7 +214,7 @@ export async function createConfig( : 'static/[name].[chunkhash:8].chunk.js', ...(isDev ? { - devtoolModuleFilenameTemplate: info => + devtoolModuleFilenameTemplate: (info: any) => `file:///${resolvePath(info.absoluteResourcePath).replace( /\\/g, '/', @@ -237,7 +251,7 @@ export async function createBackendConfig( ? { watch: true, watchOptions: { - ignored: [/node_modules\/(?!\@backstage)/], + ignored: /node_modules\/(?!\@backstage)/, }, } : {}), @@ -259,7 +273,7 @@ export async function createBackendConfig( performance: { hints: false, // we check the gzip size instead }, - devtool: isDev ? 'cheap-module-eval-source-map' : 'source-map', + devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map', context: paths.targetPath, entry: [ 'webpack/hot/poll?100', @@ -291,7 +305,7 @@ export async function createBackendConfig( : '[name].[chunkhash:8].chunk.js', ...(isDev ? { - devtoolModuleFilenameTemplate: info => + devtoolModuleFilenameTemplate: (info: any) => `file:///${resolvePath(info.absoluteResourcePath).replace( /\\/g, '/', @@ -300,7 +314,7 @@ export async function createBackendConfig( : {}), }, plugins: [ - new StartServerPlugin({ + new RunScriptWebpackPlugin({ name: 'main.js', nodeArgs: options.inspectEnabled ? ['--inspect'] : undefined, }), @@ -308,10 +322,9 @@ export async function createBackendConfig( ...(checksEnabled ? [ new ForkTsCheckerWebpackPlugin({ - typescript: { - configFile: paths.targetTsConfig, - }, - eslint: { + typescript: paths.targetTsConfig, + eslint: true, + eslintOptions: { files: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'], options: { parserOptions: { @@ -347,11 +360,10 @@ function nodeExternalsWithResolve( }); return ( - context: string, - request: string, - callback: webpack.ExternalsFunctionCallback, + { context, request }: { context?: string; request?: string }, + callback: any, ) => { - currentContext = context; + currentContext = context!; return externals(context, request, callback); }; } diff --git a/packages/cli/src/lib/bundler/optimization.ts b/packages/cli/src/lib/bundler/optimization.ts index e97b2ae868..b8164d263d 100644 --- a/packages/cli/src/lib/bundler/optimization.ts +++ b/packages/cli/src/lib/bundler/optimization.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { Options } from 'webpack'; +import { WebpackOptionsNormalized, WebpackPluginInstance } from 'webpack'; import TerserPlugin from 'terser-webpack-plugin'; import { BundlingOptions } from './types'; import { isParallelDefault } from '../parallel'; export const optimization = ( options: BundlingOptions, -): Options.Optimization => { +): WebpackOptionsNormalized['optimization'] => { const { isDev } = options; return { @@ -32,7 +32,7 @@ export const optimization = ( minimizer: [ new TerserPlugin({ parallel: options.parallel, - }), + }) as unknown as WebpackPluginInstance, ], } : {}), diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 088128c805..2f8e3125c1 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -42,25 +42,31 @@ export async function serveBundle(options: ServeOptions) { }); const compiler = webpack(config); - const server = new WebpackDevServer(compiler, { - hot: !process.env.CI, - contentBase: paths.targetPublic, - contentBasePublicPath: config.output?.publicPath, - publicPath: config.output?.publicPath, - historyApiFallback: { - // Paths with dots should still use the history fallback. - // See https://github.com/facebookincubator/create-react-app/issues/387. - disableDotRule: true, - }, - clientLogLevel: 'warning', - stats: 'errors-warnings', - https: url.protocol === 'https:', - host, - port, - proxy: pkg.proxy, - // When the dev server is behind a proxy, the host and public hostname differ - allowedHosts: [url.hostname], - }); + const server = new WebpackDevServer( + compiler as any, + { + hot: !process.env.CI, + devMiddleware: { + publicPath: config.output?.publicPath as string, + stats: 'errors-warnings', + }, + static: { + publicPath: config.output?.publicPath as string, + directory: paths.targetPublic ?? '/', + }, + historyApiFallback: { + // Paths with dots should still use the history fallback. + // See https://github.com/facebookincubator/create-react-app/issues/387. + disableDotRule: true, + }, + https: url.protocol === 'https:', + host, + port, + proxy: pkg.proxy, + // When the dev server is behind a proxy, the host and public hostname differ + allowedHosts: [url.hostname], + } as any, + ); await new Promise((resolve, reject) => { server.listen(port, host, (err?: Error) => { diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index 6eb4920326..34a441139c 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import webpack, { Module, Plugin } from 'webpack'; +import webpack, { ModuleOptions, WebpackPluginInstance } from 'webpack'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import { svgrTemplate } from '../svgrTemplate'; type Transforms = { - loaders: Module['rules']; - plugins: Plugin[]; + loaders: ModuleOptions['rules']; + plugins: WebpackPluginInstance[]; }; type TransformOptions = { @@ -51,6 +51,12 @@ export const transforms = (options: TransformOptions): Transforms => { production: !isDev, }, }, + { + test: /\.m?js/, + resolve: { + fullySpecified: false, + }, + }, { test: [/\.icon\.svg$/], use: [ @@ -105,7 +111,7 @@ export const transforms = (options: TransformOptions): Transforms => { }, ]; - const plugins = new Array(); + const plugins = new Array(); if (isDev) { plugins.push(new webpack.HotModuleReplacementPlugin()); diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 7b23652c33..d3517e3c83 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -137,9 +137,9 @@ export class Lockfile { } // Find all versions currently in use - const versions = Array.from( - new Set(entries.map(e => e.version)), - ).sort((v1, v2) => semver.rcompare(v1, v2)); + const versions = Array.from(new Set(entries.map(e => e.version))).sort( + (v1, v2) => semver.rcompare(v1, v2), + ); // If we're not using at least 2 different versions we're done if (versions.length < 2) { diff --git a/packages/cli/src/types.d.ts b/packages/cli/src/types.d.ts index 9088a86369..db5e508ec2 100644 --- a/packages/cli/src/types.d.ts +++ b/packages/cli/src/types.d.ts @@ -29,5 +29,230 @@ declare module '@svgr/rollup' { } declare module '@rollup/plugin-yaml'; +declare module 'react-dev-utils/formatWebpackMessages' { + export default function (stats: any): { + errors: string[]; + warnings: string[]; + }; +} -declare module 'terser-webpack-plugin'; +declare module 'react-dev-utils/openBrowser' { + export default function (url: string): boolean; +} + +declare module 'react-dev-utils/ModuleScopePlugin' { + import webpack = require('webpack'); + + export default class ModuleScopePlugin + implements webpack.WebpackPluginInstance + { + constructor( + appSrc: string | ReadonlyArray, + allowedFiles?: ReadonlyArray, + ); + apply: (resolver: any) => void; + } +} + +declare module 'react-dev-utils/FileSizeReporter' { + import webpack = require('webpack'); + + export interface OpaqueFileSizes { + root: string; + sizes: Record; + } + + /** + * Captures JS and CSS asset sizes inside the passed `buildFolder`. Save the + * result value to compare it after the build. + */ + export function measureFileSizesBeforeBuild( + buildFolder: string, + ): Promise; + + /** + * Prints the JS and CSS asset sizes after the build, and includes a size + * comparison with `previousFileSizes` that were captured earlier using + * `measureFileSizesBeforeBuild()`. `maxBundleGzipSize` and + * `maxChunkGzipSizemay` may optionally be specified to display a warning when + * the main bundle or a chunk exceeds the specified size (in bytes). + */ + export function printFileSizesAfterBuild( + webpackStats: webpack.Stats, + previousFileSizes: OpaqueFileSizes, + buildFolder: string, + maxBundleGzipSize?: number, + maxChunkGzipSize?: number, + ): void; +} + +declare module 'mini-css-extract-plugin' { + import webpack = require('webpack'); + + /** + * Lightweight CSS extraction webpack plugin. + * + * This plugin extracts CSS into separate files. It creates a CSS file per JS file which + * contains CSS. It supports On-Demand-Loading of CSS and SourceMaps. + * + * Configuration Detail: https://github.com/webpack-contrib/mini-css-extract-plugin#configuration + */ + export default class MiniCssExtractPlugin { + /** + * Webpack loader always used at the end of loaders list (ie. array index zero). + */ + static loader: string; + + constructor(options?: MiniCssExtractPlugin.PluginOptions); + + /** + * Apply the plugin + */ + apply(compiler: webpack.Compiler): void; + } + + namespace MiniCssExtractPlugin { + interface PluginOptions { + /** + * Works like [`output.filename`](https://webpack.js.org/configuration/output/#outputfilename). + */ + filename?: Required['output']['filename']; + /** + * Works like [`output.chunkFilename`](https://webpack.js.org/configuration/output/#outputchunkfilename). + */ + chunkFilename?: string; + /** + * For projects where CSS ordering has been mitigated through consistent + * use of scoping or naming conventions, the CSS order warnings can be + * disabled by setting this flag to true for the plugin. + */ + ignoreOrder?: boolean; + /** + * Specify where to insert the link tag. + * + * A string value specifies a DOM query for a parent element to attach to. + * + * A function allows to override default behavior for non-entry CSS chunks. + * This code will run in the browser alongside your application. It is recommend + * to only use ECMA 5 features and syntax. The function won't have access to the + * scope of the webpack configuration module. + * + * @default function() { document.head.appendChild(linkTag); } + */ + insert?: string | ((linkTag: any) => void); + /** + * Specify additional html attributes to add to the link tag. + * + * Note: These are only applied to dynamically loaded css chunks. To modify link + * attributes for entry CSS chunks, please use html-webpack-plugin. + */ + attributes?: Record; + /** + * This option allows loading asynchronous chunks with a custom link type, such as + * ``. + * + * `false` disables the link `type` attribute. + * + * @default 'text/css' + */ + linkType?: string | false | 'text/css'; + } + interface LoaderOptions { + /** + * Overrides [`output.publicPath`](https://webpack.js.org/configuration/output/#outputpublicpath). + * @default output.publicPath + */ + publicPath?: string | ((resourcePath: string, context: string) => string); + /** + * If false, the plugin will extract the CSS but **will not** emit the file + * @default true + */ + emit?: boolean; + /** + * By default, `mini-css-extract-plugin` generates JS modules that use the ES modules syntax. + * There are some cases in which using ES modules is beneficial, + * like in the case of module concatenation and tree shaking. + * @default true + */ + esModule?: boolean; + modules?: { + /** + * Enables/disables ES modules named export for locals. + * + * Names of locals are converted to camelCase. It is not allowed to use + * JavaScript reserved words in CSS class names. Options `esModule` and + * `modules.namedExport` in css-loader and MiniCssExtractPlugin.loader + * must be enabled. + * + * @default false + */ + namedExport?: boolean; + }; + } + } +} + +declare module 'fork-ts-checker-webpack-plugin/lib/ForkTsCheckerWebpackPlugin' {} +declare module 'webpack-node-externals' { + export default function webpackNodeExternals( + options?: webpackNodeExternals.Options, + ): any; + + namespace webpackNodeExternals { + type AllowlistOption = string | RegExp | AllowlistFunctionType; + type ImportTypeCallback = (moduleName: string) => string; + /** a function that accepts the module name and returns whether it should be included */ + type AllowlistFunctionType = (moduleName: string) => boolean; + interface ModulesFromFileType { + exclude?: string | string[]; + include?: string | string[]; + } + + interface Options { + /** + * An array for the externals to allow, so they will be included in the bundle. + * Can accept exact strings ('module_name'), regex patterns (/^module_name/), or a + * function that accepts the module name and returns whether it should be included. + * Important - if you have set aliases in your webpack config with the exact + * same names as modules in node_modules, you need to allowlist them so Webpack will know + * they should be bundled. + * @default [] + */ + allowlist?: AllowlistOption[] | AllowlistOption; + /** + * @default ['.bin'] + */ + binaryDirs?: string[]; + /** + * The method in which unbundled modules will be required in the code. Best to leave as + * 'commonjs' for node modules. + * @default 'commonjs' + */ + importType?: + | 'var' + | 'this' + | 'commonjs' + | 'amd' + | 'umd' + | ImportTypeCallback; + /** + * The folder in which to search for the node modules. + * @default 'node_modules' + */ + modulesDir?: string; + /** + * Additional folders to look for node modules. + */ + additionalModuleDirs?: string[]; + /** + * Read the modules from the package.json file instead of the node_modules folder. + * @default false + */ + modulesFromFile?: boolean | ModulesFromFileType; + /** + * @default false + */ + includeAbsolutePaths?: boolean; + } + } +} diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 820780ced3..b709350a73 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/codemods +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.3.0 + - @backstage/core-plugin-api@0.1.5 + - @backstage/core-app-api@0.1.7 + ## 0.1.7 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 18c621a523..38602ca03d 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.7", + "version": "0.1.8", "private": false, "publishConfig": { "access": "public", diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index d06568bf47..44819b6903 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/config-loader +## 0.6.6 + +### Patch Changes + +- e9d3983ee: Add option to populate the `filteredKeys` property when processing configuration with a schema. +- Updated dependencies + - @backstage/config@0.1.6 + ## 0.6.5 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 7eb0a78f3f..33b8acc3b9 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.6.5", + "version": "0.6.6", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/cli-common": "^0.1.1", - "@backstage/config": "^0.1.5", + "@backstage/config": "^0.1.6", "@types/json-schema": "^7.0.6", "ajv": "^7.0.3", "fs-extra": "9.1.0", diff --git a/packages/config-loader/src/lib/transform/include.test.ts b/packages/config-loader/src/lib/transform/include.test.ts index 44bf07ab4d..91df723621 100644 --- a/packages/config-loader/src/lib/transform/include.test.ts +++ b/packages/config-loader/src/lib/transform/include.test.ts @@ -24,9 +24,11 @@ const substituteMe = '${MY_SUBSTITUTION}'; const mySubstitution = 'fooSubstitution'; const env = jest.fn(async (name: string) => { - return ({ - SECRET: 'my-secret', - } as { [name: string]: string })[name]; + return ( + { + SECRET: 'my-secret', + } as { [name: string]: string } + )[name]; }); const substitute: TransformFunc = async value => { @@ -44,14 +46,16 @@ const substitute: TransformFunc = async value => { }; const readFile = jest.fn(async (path: string) => { - const content = ({ - [resolvePath(root, 'my-secret')]: 'secret', - [resolvePath(root, 'my-data.json')]: '{"a":{"b":{"c":42}}}', - [resolvePath(root, 'my-data.yaml')]: 'some:\n yaml:\n key: 7', - [resolvePath(root, 'my-data.yml')]: 'different: { key: hello }', - [resolvePath(root, 'invalid.yaml')]: 'foo: [}', - [resolvePath(root, `${mySubstitution}/my-data.json`)]: '{"foo":"bar"}', - } as { [key: string]: string })[path]; + const content = ( + { + [resolvePath(root, 'my-secret')]: 'secret', + [resolvePath(root, 'my-data.json')]: '{"a":{"b":{"c":42}}}', + [resolvePath(root, 'my-data.yaml')]: 'some:\n yaml:\n key: 7', + [resolvePath(root, 'my-data.yml')]: 'different: { key: hello }', + [resolvePath(root, 'invalid.yaml')]: 'foo: [}', + [resolvePath(root, `${mySubstitution}/my-data.json`)]: '{"foo":"bar"}', + } as { [key: string]: string } + )[path]; if (!content) { throw new Error('File not found!'); diff --git a/packages/config-loader/src/lib/transform/substitution.test.ts b/packages/config-loader/src/lib/transform/substitution.test.ts index dc6ed47597..b03c39c174 100644 --- a/packages/config-loader/src/lib/transform/substitution.test.ts +++ b/packages/config-loader/src/lib/transform/substitution.test.ts @@ -17,10 +17,12 @@ import { createSubstitutionTransform } from './substitution'; const env = jest.fn(async (name: string) => { - return ({ - SECRET: 'my-secret', - TOKEN: 'my-token', - } as { [name: string]: string })[name]; + return ( + { + SECRET: 'my-secret', + TOKEN: 'my-token', + } as { [name: string]: string } + )[name]; }); const substituteTransform = createSubstitutionTransform(env); diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 5cababf94e..63aa34b034 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/config +## 0.1.6 + +### Patch Changes + +- e9d3983ee: Add warning when trying to access configuration values that have been filtered out by visibility. + ## 0.1.5 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 400e4548cd..5d9bf94446 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "0.1.5", + "version": "0.1.6", "private": false, "publishConfig": { "access": "public", diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index ed0ca7febe..2f846fe159 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -64,9 +64,9 @@ function expectValidValues(config: ConfigReader) { strings: ['string1', 'string2'], }); expect(config.getConfig('nested').getString('string')).toBe('string'); - expect( - config.getOptionalConfig('nested')!.getStringArray('strings'), - ).toEqual(['string1', 'string2']); + expect(config.getOptionalConfig('nested')!.getStringArray('strings')).toEqual( + ['string1', 'string2'], + ); expect(config.getOptional('missing')).toBe(undefined); expect(config.getOptionalConfig('missing')).toBe(undefined); expect(config.getOptionalConfigArray('missing')).toBe(undefined); @@ -234,9 +234,9 @@ describe('ConfigReader', () => { expect(withLogCollector(() => config.getOptional('a'))).toMatchObject({ warn: [], }); - expect( - withLogCollector(() => config.getOptionalString('a')), - ).toMatchObject({ warn: [] }); + expect(withLogCollector(() => config.getOptionalString('a'))).toMatchObject( + { warn: [] }, + ); expect( withLogCollector(() => config.getOptionalConfigArray('b')), ).toMatchObject({ warn: [] }); diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 904932a9b5..dbb26fe2a0 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/core-app-api +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.3.0 + - @backstage/config@0.1.6 + - @backstage/core-plugin-api@0.1.5 + ## 0.1.6 ### Patch Changes diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index a7a3845dd7..e2c6b3a939 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -68,9 +68,7 @@ export class AlertApiForwarder implements AlertApi { // // @public (undocumented) export type ApiFactoryHolder = { - get( - api: ApiRef, - ): + get(api: ApiRef): | ApiFactory< T, T, @@ -86,9 +84,7 @@ export type ApiFactoryHolder = { // @public export class ApiFactoryRegistry implements ApiFactoryHolder { // (undocumented) - get( - api: ApiRef, - ): + get(api: ApiRef): | ApiFactory< T, T, @@ -105,7 +101,7 @@ export class ApiFactoryRegistry implements ApiFactoryHolder { Impl extends Api, Deps extends { [name in string]: unknown; - } + }, >(scope: ApiFactoryScope, factory: ApiFactory): boolean; } @@ -211,7 +207,7 @@ export type AppOptions = { export type AppRouteBinder = < ExternalRoutes extends { [name: string]: ExternalRouteRef; - } + }, >( externalRoutes: ExternalRoutes, targetRoutes: PartialKeys< @@ -469,7 +465,8 @@ export class OAuth2 OpenIdConnectApi, ProfileInfoApi, BackstageIdentityApi, - SessionApi { + SessionApi +{ // Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts constructor(options: Options); // Warning: (ae-forgotten-export) The symbol "CreateOptions" needs to be exported by the entry point index.d.ts @@ -561,7 +558,8 @@ export class OneLoginAuth { // // @public (undocumented) export class SamlAuth - implements ProfileInfoApi, BackstageIdentityApi, SessionApi { + implements ProfileInfoApi, BackstageIdentityApi, SessionApi +{ // Warning: (ae-forgotten-export) The symbol "SamlSession" needs to be exported by the entry point index.d.ts constructor(sessionManager: SessionManager); // Warning: (ae-forgotten-export) The symbol "AuthApiCreateOptions" needs to be exported by the entry point index.d.ts diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index d49d89eab6..204550323c 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "0.1.6", + "version": "0.1.7", "private": false, "publishConfig": { "access": "public", @@ -29,13 +29,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.2.0", - "@backstage/config": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.4", + "@backstage/core-components": "^0.3.0", + "@backstage/config": "^0.1.6", + "@backstage/core-plugin-api": "^0.1.5", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@types/react": "^16.9", + "@types/react": "*", "@types/prop-types": "^15.7.3", "prop-types": "^15.7.2", "react": "^16.12.0", @@ -44,7 +44,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.6", + "@backstage/cli": "^0.7.7", "@backstage/test-utils": "^0.1.16", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx b/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx index afc25b2e8c..1193592e96 100644 --- a/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx +++ b/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx @@ -194,8 +194,7 @@ describe('FeatureFlags', () => { it('throws an error if length is greater than 150 characters', () => { expect(() => featureFlags.registerFlag({ - name: - 'loremipsumdolorsitametconsecteturadipiscingelitnuncvitaeportaexaullamcorperturpismaurisutmattisnequemorbisediaculisauguevivamuspulvinarcursuseratblandithendreritquisqueuttinciduntmagnavestibulumblanditaugueat', + name: 'loremipsumdolorsitametconsecteturadipiscingelitnuncvitaeportaexaullamcorperturpismaurisutmattisnequemorbisediaculisauguevivamuspulvinarcursuseratblandithendreritquisqueuttinciduntmagnavestibulumblanditaugueat', pluginId: 'plugin-three', }), ).toThrow(/not exceed 150 characters/i); diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index 8c27e0aa63..24db8d2560 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -65,7 +65,8 @@ class OAuth2 OpenIdConnectApi, ProfileInfoApi, BackstageIdentityApi, - SessionApi { + SessionApi +{ static create({ discoveryApi, environment = 'development', diff --git a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx index 5afb39a2ec..b94aea6b67 100644 --- a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx +++ b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx @@ -185,9 +185,10 @@ describe('ApiProvider', () => { }); describe('v1 consumer', () => { - const ApiContext = getGlobalSingleton< - Context> - >('api-context'); + const ApiContext = + getGlobalSingleton>>( + 'api-context', + ); function useMockApiV1(apiRef: ApiRef): T { const impl = useContext(ApiContext)?.atVersion(1)?.get(apiRef); diff --git a/packages/core-app-api/src/app/App.tsx b/packages/core-app-api/src/app/App.tsx index a95554709a..c8bd8fec8f 100644 --- a/packages/core-app-api/src/app/App.tsx +++ b/packages/core-app-api/src/app/App.tsx @@ -107,6 +107,20 @@ export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) { return result; } +/** + * Get the app base path from the configured app baseUrl. + * + * The returned path does not have a trailing slash. + */ +function getBasePath(configApi: Config) { + let { pathname } = new URL( + configApi.getOptionalString('app.baseUrl') ?? '/', + 'http://dummy.dev', // baseUrl can be specified as just a path + ); + pathname = pathname.replace(/\/*$/, ''); + return pathname; +} + type FullAppOptions = { apis: Iterable; icons: NonNullable; @@ -216,37 +230,33 @@ export class PrivateAppImpl implements BackstageApp { [], ); - const { - routePaths, - routeParents, - routeObjects, - featureFlags, - } = useMemo(() => { - const result = traverseElementTree({ - root: children, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routePaths: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - collectedPlugins: pluginCollector, - featureFlags: featureFlagCollector, - }, - }); + const { routePaths, routeParents, routeObjects, featureFlags } = + useMemo(() => { + const result = traverseElementTree({ + root: children, + discoverers: [childDiscoverer, routeElementDiscoverer], + collectors: { + routePaths: routePathCollector, + routeParents: routeParentCollector, + routeObjects: routeObjectCollector, + collectedPlugins: pluginCollector, + featureFlags: featureFlagCollector, + }, + }); - validateRoutes(result.routePaths, result.routeParents); + validateRoutes(result.routePaths, result.routeParents); - // TODO(Rugvip): Restructure the public API so that we can get an immediate view of - // the app, rather than having to wait for the provider to render. - // For now we need to push the additional plugins we find during - // collection and then make sure we initialize things afterwards. - result.collectedPlugins.forEach(plugin => this.plugins.add(plugin)); - this.verifyPlugins(this.plugins); + // TODO(Rugvip): Restructure the public API so that we can get an immediate view of + // the app, rather than having to wait for the provider to render. + // For now we need to push the additional plugins we find during + // collection and then make sure we initialize things afterwards. + result.collectedPlugins.forEach(plugin => this.plugins.add(plugin)); + this.verifyPlugins(this.plugins); - // Initialize APIs once all plugins are available - this.getApiHolder(); - return result; - }, [children]); + // Initialize APIs once all plugins are available + this.getApiHolder(); + return result; + }, [children]); const loadedConfig = useConfigLoader( this.configLoader, @@ -302,6 +312,7 @@ export class PrivateAppImpl implements BackstageApp { routeParents={routeParents} routeObjects={routeObjects} routeBindings={generateBoundRoutes(this.bindRoutes)} + basePath={getBasePath(loadedConfig.api)} > {children} @@ -314,10 +325,8 @@ export class PrivateAppImpl implements BackstageApp { } getRouter(): ComponentType<{}> { - const { - Router: RouterComponent, - SignInPage: SignInPageComponent, - } = this.components; + const { Router: RouterComponent, SignInPage: SignInPageComponent } = + this.components; // This wraps the sign-in page and waits for sign-in to be completed before rendering the app const SignInPageWrapper = ({ @@ -339,14 +348,7 @@ export class PrivateAppImpl implements BackstageApp { const AppRouter = ({ children }: PropsWithChildren<{}>) => { const configApi = useApi(configApiRef); - - let { pathname } = new URL( - configApi.getOptionalString('app.baseUrl') ?? '/', - 'http://dummy.dev', // baseUrl can be specified as just a path - ); - if (pathname.endsWith('/')) { - pathname = pathname.replace(/\/$/, ''); - } + const mountPath = `${getBasePath(configApi)}/*`; // If the app hasn't configured a sign-in page, we just continue as guest. if (!SignInPageComponent) { @@ -361,7 +363,7 @@ export class PrivateAppImpl implements BackstageApp { return ( - {children}} /> + {children}} /> ); @@ -371,7 +373,7 @@ export class PrivateAppImpl implements BackstageApp { - {children}} /> + {children}} /> diff --git a/packages/core-app-api/src/app/AppContext.test.tsx b/packages/core-app-api/src/app/AppContext.test.tsx index d87bf7d228..6ce164b9f0 100644 --- a/packages/core-app-api/src/app/AppContext.test.tsx +++ b/packages/core-app-api/src/app/AppContext.test.tsx @@ -22,9 +22,10 @@ import { AppContext as AppContextV1 } from './types'; import { AppContextProvider } from './AppContext'; describe('v1 consumer', () => { - const AppContext = getGlobalSingleton< - Context> - >('app-context'); + const AppContext = + getGlobalSingleton>>( + 'app-context', + ); function useMockAppV1(): AppContextV1 { const impl = useContext(AppContext)?.atVersion(1); diff --git a/packages/core-app-api/src/app/createApp.tsx b/packages/core-app-api/src/app/createApp.tsx index b7c49939e2..1d74f93add 100644 --- a/packages/core-app-api/src/app/createApp.tsx +++ b/packages/core-app-api/src/app/createApp.tsx @@ -60,7 +60,7 @@ export const defaultConfigLoader: AppConfigLoader = async ( if (!Array.isArray(appConfig)) { throw new Error('Static configuration has invalid format'); } - const configs = (appConfig.slice() as unknown) as AppConfig[]; + const configs = appConfig.slice() as unknown as AppConfig[]; // Avoiding this string also being replaced at runtime if ( diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 836f1b7fb0..30ce45abe9 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -105,14 +105,14 @@ type KeysWithType = { */ type PartialKeys< Map extends { [name in string]: any }, - Keys extends keyof Map + Keys extends keyof Map, > = Partial> & Required>; /** * Creates a map of target routes with matching parameters based on a map of external routes. */ type TargetRouteMap< - ExternalRoutes extends { [name: string]: ExternalRouteRef } + ExternalRoutes extends { [name: string]: ExternalRouteRef }, > = { [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef< infer Params, @@ -123,7 +123,7 @@ type TargetRouteMap< }; export type AppRouteBinder = < - ExternalRoutes extends { [name: string]: ExternalRouteRef } + ExternalRoutes extends { [name: string]: ExternalRouteRef }, >( externalRoutes: ExternalRoutes, targetRoutes: PartialKeys< diff --git a/packages/core-app-api/src/extensions/extensions.tsx b/packages/core-app-api/src/extensions/extensions.tsx index 063f34c659..199430c619 100644 --- a/packages/core-app-api/src/extensions/extensions.tsx +++ b/packages/core-app-api/src/extensions/extensions.tsx @@ -28,7 +28,7 @@ const ERROR_MESSAGE = 'Import this from @backstage/core-plugin-api'; /** @deprecated Import from @backstage/core-plugin-api instead */ export function createRoutableExtension< - T extends (props: any) => JSX.Element | null + T extends (props: any) => JSX.Element | null, >(_options: { component: () => Promise; mountPoint: RouteRef; @@ -38,14 +38,14 @@ export function createRoutableExtension< /** @deprecated Import from @backstage/core-plugin-api instead */ export function createComponentExtension< - T extends (props: any) => JSX.Element | null + T extends (props: any) => JSX.Element | null, >(_options: { component: ComponentLoader }): Extension { throw new Error(ERROR_MESSAGE); } /** @deprecated Import from @backstage/core-plugin-api instead */ export function createReactExtension< - T extends (props: any) => JSX.Element | null + T extends (props: any) => JSX.Element | null, >(_options: { component: ComponentLoader; data?: Record; diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts index 524a0c5709..ce5df9fc91 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -126,8 +126,7 @@ describe('DefaultAuthConnector', () => { expect(popupSpy).toBeCalledTimes(1); expect(popupSpy.mock.calls[0][0]).toMatchObject({ - url: - 'http://my-host/api/auth/my-provider/start?scope=a%20b&env=production', + url: 'http://my-host/api/auth/my-provider/start?scope=a%20b&env=production', }); await expect(sessionPromise).resolves.toEqual({ @@ -175,8 +174,7 @@ describe('DefaultAuthConnector', () => { expect(popupSpy).toBeCalledTimes(1); expect(popupSpy.mock.calls[0][0]).toMatchObject({ - url: - 'http://my-host/api/auth/my-provider/start?scope=-ab-&env=production', + url: 'http://my-host/api/auth/my-provider/start?scope=-ab-&env=production', }); }); }); diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts index 81da04010c..d707acdc9b 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts @@ -61,7 +61,8 @@ function defaultJoinScopes(scopes: Set) { * via the OAuthRequestApi. */ export class DefaultAuthConnector - implements AuthConnector { + implements AuthConnector +{ private readonly discoveryApi: DiscoveryApi; private readonly environment: string; private readonly provider: AuthProvider & { id: string }; diff --git a/packages/core-app-api/src/lib/loginPopup.test.ts b/packages/core-app-api/src/lib/loginPopup.test.ts index 1eb7c3f8b9..db4d9eb8eb 100644 --- a/packages/core-app-api/src/lib/loginPopup.test.ts +++ b/packages/core-app-api/src/lib/loginPopup.test.ts @@ -30,8 +30,7 @@ describe('showLoginPopup', () => { const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); const payloadPromise = showLoginPopup({ - url: - 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', + url: 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', name: 'test-popup', origin: 'my-origin', }); diff --git a/packages/core-app-api/src/lib/subjects.ts b/packages/core-app-api/src/lib/subjects.ts index 0391050bda..e122da819b 100644 --- a/packages/core-app-api/src/lib/subjects.ts +++ b/packages/core-app-api/src/lib/subjects.ts @@ -29,7 +29,8 @@ import ObservableImpl from 'zen-observable'; * See http://reactivex.io/documentation/subject.html */ export class PublishSubject - implements Observable, ZenObservable.SubscriptionObserver { + implements Observable, ZenObservable.SubscriptionObserver +{ private isClosed = false; private terminatingError?: Error; @@ -121,7 +122,8 @@ export class PublishSubject * See http://reactivex.io/documentation/subject.html */ export class BehaviorSubject - implements Observable, ZenObservable.SubscriptionObserver { + implements Observable, ZenObservable.SubscriptionObserver +{ private isClosed = false; private currentValue: T; private terminatingError?: Error; diff --git a/packages/core-app-api/src/lib/versionedValues.ts b/packages/core-app-api/src/lib/versionedValues.ts index 3d0a4a41ae..3b3064fc7e 100644 --- a/packages/core-app-api/src/lib/versionedValues.ts +++ b/packages/core-app-api/src/lib/versionedValues.ts @@ -29,7 +29,7 @@ export type VersionedValue = { * Creates a container for a map of versioned values that implements VersionedValue. */ export function createVersionedValueMap< - Versions extends { [version: number]: any } + Versions extends { [version: number]: any }, >(versions: Versions): VersionedValue { Object.freeze(versions); return { diff --git a/packages/core-app-api/src/routing/FlatRoutes.test.tsx b/packages/core-app-api/src/routing/FlatRoutes.test.tsx index d5201a6f57..0df296cc59 100644 --- a/packages/core-app-api/src/routing/FlatRoutes.test.tsx +++ b/packages/core-app-api/src/routing/FlatRoutes.test.tsx @@ -37,11 +37,11 @@ function makeRouteRenderer(node: ReactNode) { ({ NotFoundErrorPage: () => <>Not Found, }), - } as unknown) as AppContext + } as unknown as AppContext } > diff --git a/packages/core-app-api/src/routing/RouteResolver.test.ts b/packages/core-app-api/src/routing/RouteResolver.test.ts index b47a18ecce..1b7eb10003 100644 --- a/packages/core-app-api/src/routing/RouteResolver.test.ts +++ b/packages/core-app-api/src/routing/RouteResolver.test.ts @@ -65,7 +65,7 @@ const externalRef4 = createExternalRouteRef({ describe('RouteResolver', () => { it('should not resolve anything with an empty resolver', () => { - const r = new RouteResolver(new Map(), new Map(), [], new Map()); + const r = new RouteResolver(new Map(), new Map(), [], new Map(), ''); expect(r.resolve(ref1, '/')?.()).toBe(undefined); expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined); @@ -85,6 +85,7 @@ describe('RouteResolver', () => { new Map(), [{ routeRefs: new Set([ref1]), path: '/my-route', ...rest }], new Map(), + '', ); expect(r.resolve(ref1, '/')?.()).toBe('/my-route'); @@ -99,6 +100,29 @@ describe('RouteResolver', () => { expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined); }); + it('should resolve an absolute route and an app base path', () => { + const r = new RouteResolver( + new Map([[ref1, '/my-route']]), + new Map(), + [{ routeRefs: new Set([ref1]), path: '/my-route', ...rest }], + new Map(), + '/base', + ); + + expect(r.resolve(ref1, '/')?.()).toBe('/base/my-route'); + expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined); + expect(r.resolve(subRef1, '/')?.()).toBe('/base/my-route/foo'); + expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe( + '/base/my-route/foo/2a', + ); + expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined); + expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined); + expect(r.resolve(externalRef1, '/')?.()).toBe(undefined); + expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); + expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined); + expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined); + }); + it('should resolve an absolute route with a param and with a parent', () => { const r = new RouteResolver( new Map([ @@ -122,6 +146,7 @@ describe('RouteResolver', () => { [externalRef3, ref2], [externalRef4, subRef3], ]), + '', ); expect(r.resolve(ref1, '/')?.()).toBe('/my-route'); @@ -179,6 +204,7 @@ describe('RouteResolver', () => { }, ], new Map(), + '', ); expect(r.resolve(ref2, '/')?.({ x: 'x' })).toBe('/root/x'); @@ -232,6 +258,7 @@ describe('RouteResolver', () => { [externalRef3, ref2], [externalRef4, subRef3], ]), + '', ); const l = '/my-grandparent/my-y/my-parent/my-x'; diff --git a/packages/core-app-api/src/routing/RouteResolver.ts b/packages/core-app-api/src/routing/RouteResolver.ts index e09da28df8..b5be5f893e 100644 --- a/packages/core-app-api/src/routing/RouteResolver.ts +++ b/packages/core-app-api/src/routing/RouteResolver.ts @@ -187,6 +187,7 @@ export class RouteResolver { ExternalRouteRef, RouteRef | SubRouteRef >, + private readonly appBasePath: string, // base path without a trailing slash ) {} resolve( @@ -209,13 +210,15 @@ export class RouteResolver { // Next we figure out the base path, which is the combination of the common parent path // between our current location and our target location, as well as the additional path // that is the difference between the parent path and the base of our target location. - const basePath = resolveBasePath( - targetRef, - sourceLocation, - this.routePaths, - this.routeParents, - this.routeObjects, - ); + const basePath = + this.appBasePath + + resolveBasePath( + targetRef, + sourceLocation, + this.routePaths, + this.routeParents, + this.routeObjects, + ); const routeFunc: RouteFunc = (...[params]) => { return basePath + generatePath(targetPath, params); diff --git a/packages/core-app-api/src/routing/RoutingProvider.test.tsx b/packages/core-app-api/src/routing/RoutingProvider.test.tsx index 499b81a743..47f9c87c96 100644 --- a/packages/core-app-api/src/routing/RoutingProvider.test.tsx +++ b/packages/core-app-api/src/routing/RoutingProvider.test.tsx @@ -152,6 +152,7 @@ function withRoutingProvider( routeParents={routeParents} routeObjects={routeObjects} routeBindings={new Map(routeBindings)} + basePath="" > {root} @@ -330,9 +331,10 @@ describe('discovery', () => { }); describe('v1 consumer', () => { - const RoutingContext = getGlobalSingleton< - Context> - >('routing-context'); + const RoutingContext = + getGlobalSingleton>>( + 'routing-context', + ); function useMockRouteRefV1( routeRef: AnyRouteRef, @@ -367,6 +369,7 @@ describe('v1 consumer', () => { routeParents={new Map()} routeObjects={[]} routeBindings={new Map()} + basePath="/base" children={children} /> ), @@ -375,8 +378,8 @@ describe('v1 consumer', () => { expect(renderedHook.result.current).toBe(undefined); renderedHook.rerender({ routeRef: routeRef2 }); - expect(renderedHook.result.current?.()).toBe('/foo'); + expect(renderedHook.result.current?.()).toBe('/base/foo'); renderedHook.rerender({ routeRef: routeRef3 }); - expect(renderedHook.result.current?.({ x: 'my-x' })).toBe('/bar/my-x'); + expect(renderedHook.result.current?.({ x: 'my-x' })).toBe('/base/bar/my-x'); }); }); diff --git a/packages/core-app-api/src/routing/RoutingProvider.tsx b/packages/core-app-api/src/routing/RoutingProvider.tsx index ea8e8f76f8..2192f5df26 100644 --- a/packages/core-app-api/src/routing/RoutingProvider.tsx +++ b/packages/core-app-api/src/routing/RoutingProvider.tsx @@ -38,6 +38,7 @@ type ProviderProps = { routeParents: Map; routeObjects: BackstageRouteObject[]; routeBindings: Map; + basePath?: string; children: ReactNode; }; @@ -46,6 +47,7 @@ export const RoutingProvider = ({ routeParents, routeObjects, routeBindings, + basePath = '', children, }: ProviderProps) => { const resolver = new RouteResolver( @@ -53,6 +55,7 @@ export const RoutingProvider = ({ routeParents, routeObjects, routeBindings, + basePath, ); const versionedValue = createVersionedValueMap({ 1: resolver }); diff --git a/packages/core-app-api/src/routing/types.ts b/packages/core-app-api/src/routing/types.ts index 6561a0da70..a12e914aba 100644 --- a/packages/core-app-api/src/routing/types.ts +++ b/packages/core-app-api/src/routing/types.ts @@ -75,7 +75,7 @@ export function isSubRouteRef( export function isExternalRouteRef< Params extends AnyParams, - Optional extends boolean + Optional extends boolean, >( routeRef: | RouteRef diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index a7d2715f3e..cb93e38ba5 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/core-components +## 0.3.0 + +### Minor Changes + +- 7bf006210: Remove unused props from InfoCard prop type + +### Patch Changes + +- c4d8ff963: Switched frontend identity code to use `token` instead of the deprecated `idToken` field +- 7b8aa8d0d: Move the `CreateComponentButton` from the catalog plugin to the `core-components` & rename it to `CreateButton` to be reused inside the api-docs plugin & scaffolder plugin, but also future plugins. Additionally, improve responsiveness of `CreateButton` & `SupportButton` by shrinking them to `IconButtons` on smaller screens. +- 260c053b9: Fix All Material UI Warnings +- Updated dependencies + - @backstage/config@0.1.6 + - @backstage/core-plugin-api@0.1.5 + ## 0.2.0 ### Minor Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index d28d8a177e..5ae86a0030 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.2.0", + "version": "0.3.0", "private": false, "publishConfig": { "access": "public", @@ -29,17 +29,18 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.5", - "@backstage/core-plugin-api": "^0.1.4", + "@backstage/config": "^0.1.6", + "@backstage/core-plugin-api": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.9", + "@material-table/core": "^3.1.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@testing-library/react-hooks": "^3.4.2", "@types/dagre": "^0.7.44", "@types/prop-types": "^15.7.3", - "@types/react": "^16.9", + "@types/react": "*", "@types/react-sparklines": "^1.7.0", "@types/react-text-truncate": "^0.14.0", "classnames": "^2.2.6", @@ -50,7 +51,6 @@ "dagre": "^0.8.5", "immer": "^9.0.1", "lodash": "^4.17.15", - "@material-table/core": "^3.1.0", "pluralize": "^8.0.0", "prop-types": "^15.7.2", "qs": "^6.9.4", @@ -70,8 +70,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/core-app-api": "^0.1.6", - "@backstage/cli": "^0.7.6", + "@backstage/core-app-api": "^0.1.7", + "@backstage/cli": "^0.7.7", "@backstage/test-utils": "^0.1.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", @@ -84,6 +84,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/react-helmet": "^6.1.0", + "@types/react-syntax-highlighter": "^13.5.2", "@types/zen-observable": "^0.8.0" }, "files": [ diff --git a/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx b/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx index c93651e3e0..69269d687d 100644 --- a/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx +++ b/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx @@ -38,10 +38,8 @@ export function DefaultNode({ node: { id } }: RenderNodeProps) { React.useLayoutEffect(() => { // set the width to the length of the ID if (idRef.current) { - let { - height: renderedHeight, - width: renderedWidth, - } = idRef.current.getBBox(); + let { height: renderedHeight, width: renderedWidth } = + idRef.current.getBBox(); renderedHeight = Math.round(renderedHeight); renderedWidth = Math.round(renderedWidth); diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index 89f7421d2a..bf018b13c8 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -132,10 +132,8 @@ export function DependencyGraph({ container.call(zoom); - const { - width: newContainerWidth, - height: newContainerHeight, - } = node.getBoundingClientRect(); + const { width: newContainerWidth, height: newContainerHeight } = + node.getBoundingClientRect(); if (containerWidth !== newContainerWidth) { setContainerWidth(newContainerWidth); } diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx index 4e4c5ca5f7..e0f19d3536 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx @@ -71,10 +71,8 @@ export function Edge({ React.useLayoutEffect(() => { // set the label width to the actual rendered width to properly layout graph if (labelRef.current) { - let { - height: renderedHeight, - width: renderedWidth, - } = labelRef.current.getBBox(); + let { height: renderedHeight, width: renderedWidth } = + labelRef.current.getBBox(); renderedHeight = Math.round(renderedHeight); renderedWidth = Math.round(renderedWidth); diff --git a/packages/core-components/src/components/DependencyGraph/Node.tsx b/packages/core-components/src/components/DependencyGraph/Node.tsx index d9e8cbfb78..a5e1a62f09 100644 --- a/packages/core-components/src/components/DependencyGraph/Node.tsx +++ b/packages/core-components/src/components/DependencyGraph/Node.tsx @@ -46,10 +46,8 @@ export function Node({ React.useLayoutEffect(() => { // set the node width to the actual rendered width to properly layout graph if (nodeRef.current) { - let { - height: renderedHeight, - width: renderedWidth, - } = nodeRef.current.getBBox(); + let { height: renderedHeight, width: renderedWidth } = + nodeRef.current.getBBox(); renderedHeight = Math.round(renderedHeight); renderedWidth = Math.round(renderedWidth); diff --git a/packages/core-components/src/components/FeatureDiscovery/lib/useShowCallout.ts b/packages/core-components/src/components/FeatureDiscovery/lib/useShowCallout.ts index 617f1324ed..a507d210a7 100644 --- a/packages/core-components/src/components/FeatureDiscovery/lib/useShowCallout.ts +++ b/packages/core-components/src/components/FeatureDiscovery/lib/useShowCallout.ts @@ -38,9 +38,10 @@ function useCalloutStates(): { return { states, setState }; } -function useCalloutHasBeenSeen( - featureId: string, -): { seen: boolean | undefined; markSeen: () => void } { +function useCalloutHasBeenSeen(featureId: string): { + seen: boolean | undefined; + markSeen: () => void; +} { const { states, setState } = useCalloutStates(); const markSeen = useCallback(() => { @@ -50,9 +51,10 @@ function useCalloutHasBeenSeen( return { seen: states[featureId] === true, markSeen }; } -export function useShowCallout( - featureId: string, -): { show: boolean; hide: () => void } { +export function useShowCallout(featureId: string): { + show: boolean; + hide: () => void; +} { const { seen, markSeen } = useCalloutHasBeenSeen(featureId); return { show: seen === false, hide: markSeen }; } diff --git a/packages/core-components/src/components/StructuredMetadataTable/MetadataTable.tsx b/packages/core-components/src/components/StructuredMetadataTable/MetadataTable.tsx index b7790e38da..5740ba9500 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/MetadataTable.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/MetadataTable.tsx @@ -96,14 +96,14 @@ interface StyleProps extends WithStyles { children?: React.ReactNode; } -export const MetadataList = withStyles( - listStyles, -)(({ classes, children }: StyleProps) => ( -
    {children}
-)); +export const MetadataList = withStyles(listStyles)( + ({ classes, children }: StyleProps) => ( +
    {children}
+ ), +); -export const MetadataListItem = withStyles( - listItemStyles, -)(({ classes, children }: StyleProps) => ( -
  • {children}
  • -)); +export const MetadataListItem = withStyles(listItemStyles)( + ({ classes, children }: StyleProps) => ( +
  • {children}
  • + ), +); diff --git a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx index af56cb5b89..9b08123d1b 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx @@ -44,16 +44,16 @@ interface StyleProps extends WithStyles { children?: React.ReactNode; } // Sub Components -const StyledList = withStyles( - listStyle, -)(({ classes, children }: StyleProps) => ( - {children} -)); -const StyledNestedList = withStyles( - nestedListStyle, -)(({ classes, children }: StyleProps) => ( - {children} -)); +const StyledList = withStyles(listStyle)( + ({ classes, children }: StyleProps) => ( + {children} + ), +); +const StyledNestedList = withStyles(nestedListStyle)( + ({ classes, children }: StyleProps) => ( + {children} + ), +); function renderList(list: Array, nested?: boolean) { const values = list.map((item: any, index: number) => ( diff --git a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx index 531262544c..52a2c29e2d 100644 --- a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx +++ b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx @@ -20,9 +20,11 @@ import { Content } from '../../layout/Content'; import { HeaderTabs } from '../../layout/HeaderTabs'; import { SubRoute } from './types'; -export function useSelectedSubRoute( - subRoutes: SubRoute[], -): { index: number; route: SubRoute; element: JSX.Element } { +export function useSelectedSubRoute(subRoutes: SubRoute[]): { + index: number; + route: SubRoute; + element: JSX.Element; +} { const params = useParams(); const routes = subRoutes.map(({ path, children }) => ({ diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx index 6e049d1b6a..22d8bff014 100644 --- a/packages/core-components/src/components/Table/Table.tsx +++ b/packages/core-components/src/components/Table/Table.tsx @@ -285,9 +285,10 @@ export function Table({ const [filtersOpen, setFiltersOpen] = useState( calculatedInitialState.filtersOpen, ); - const toggleFilters = useCallback(() => setFiltersOpen(v => !v), [ - setFiltersOpen, - ]); + const toggleFilters = useCallback( + () => setFiltersOpen(v => !v), + [setFiltersOpen], + ); const [selectedFiltersLength, setSelectedFiltersLength] = useState(0); const [tableData, setTableData] = useState(data as any[]); const [selectedFilters, setSelectedFilters] = useState( diff --git a/packages/core-components/src/hooks/useSupportConfig.ts b/packages/core-components/src/hooks/useSupportConfig.ts index 5bab9a3c6b..5d64e38f3d 100644 --- a/packages/core-components/src/hooks/useSupportConfig.ts +++ b/packages/core-components/src/hooks/useSupportConfig.ts @@ -42,8 +42,7 @@ const DEFAULT_SUPPORT_CONFIG: SupportConfig = { { // TODO: Update to dedicated support page on backstage.io/docs title: 'Add `app.support` config key', - url: - 'https://github.com/andrewthauer/backstage/blob/master/app-config.yaml', + url: 'https://github.com/andrewthauer/backstage/blob/master/app-config.yaml', }, ], }, diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx index f8adb81a2a..a1c45bff4d 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx @@ -42,18 +42,14 @@ describe('', () => { it('should render error boundary with and without error', async () => { const { error } = await withLogCollector(['error'], async () => { const apis = ApiRegistry.with(errorApiRef, new MockErrorApi()); - const { - rerender, - queryByRole, - getByRole, - getByText, - } = await renderInTestApp( - - - - - , - ); + const { rerender, queryByRole, getByRole, getByText } = + await renderInTestApp( + + + + + , + ); expect(queryByRole('alert')).not.toBeInTheDocument(); expect(getByText(/working component/i)).toBeInTheDocument(); diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx index 1b300df5a2..20742114c9 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx @@ -36,6 +36,12 @@ const useStyles = makeStyles(theme => ({ selected: { color: theme.palette.text.primary, }, + tabRoot: { + '&:hover': { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + }, + }, })); export type Tab = { @@ -89,7 +95,7 @@ export const HeaderTabs = ({ key={tab.id} value={index} className={styles.defaultTab} - classes={{ selected: styles.selected }} + classes={{ selected: styles.selected, root: styles.tabRoot }} /> ))} diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index cc15bbd9f4..650bf78bbd 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -91,8 +91,8 @@ export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; /** * InfoCard is used to display a paper-styled block on the screen, similar to a panel. * - * You can custom style an InfoCard with the 'style' (outer container) and 'cardStyle' (inner container) - * styles. + * You can custom style an InfoCard with the 'className' (outer container) and 'cardClassName' (inner container) + * props. This is typically used with the material-ui makeStyles mechanism. * * The InfoCard serves as an error boundary. As a result, if you provide an 'errorBoundaryProps' property this * specifies the extra information to display in the error component that is displayed if an error occurs @@ -115,8 +115,6 @@ type Props = { slackChannel?: string; errorBoundaryProps?: ErrorBoundaryProps; variant?: InfoCardVariants; - style?: object; - cardStyle?: object; children?: ReactNode; headerStyle?: object; headerProps?: CardHeaderProps; diff --git a/packages/core-components/src/layout/Sidebar/Intro.tsx b/packages/core-components/src/layout/Sidebar/Intro.tsx index 1d46239865..14c5d9e25a 100644 --- a/packages/core-components/src/layout/Sidebar/Intro.tsx +++ b/packages/core-components/src/layout/Sidebar/Intro.tsx @@ -133,10 +133,8 @@ export const SidebarIntro = () => { starredItemsDismissed: false, recentlyViewedItemsDismissed: false, }; - const [ - dismissedIntro, - setDismissedIntro, - ] = useLocalStorage(SIDEBAR_INTRO_LOCAL_STORAGE); + const [dismissedIntro, setDismissedIntro] = + useLocalStorage(SIDEBAR_INTRO_LOCAL_STORAGE); const { starredItemsDismissed, recentlyViewedItemsDismissed } = dismissedIntro ?? {}; diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index b930c455b5..1068a877bd 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -34,7 +34,12 @@ import React, { useContext, useState, } from 'react'; -import { NavLink, NavLinkProps } from 'react-router-dom'; +import { + Link, + NavLinkProps, + useLocation, + useResolvedPath, +} from 'react-router-dom'; import { sidebarConfig, SidebarContext } from './config'; const useStyles = makeStyles(theme => { @@ -147,6 +152,54 @@ function isButtonItem( return (props as SidebarItemLinkProps).to === undefined; } +// TODO(Rugvip): Remove this once NavLink is updated in react-router-dom. +// This is needed because react-router doesn't handle the path comparison +// properly yet, matching for example /foobar with /foo. +export const WorkaroundNavLink = React.forwardRef< + HTMLAnchorElement, + NavLinkProps +>(function WorkaroundNavLinkWithRef( + { + to, + end, + style, + className, + activeStyle, + caseSensitive, + activeClassName = 'active', + 'aria-current': ariaCurrentProp = 'page', + ...rest + }, + ref, +) { + let { pathname: locationPathname } = useLocation(); + let { pathname: toPathname } = useResolvedPath(to); + + if (!caseSensitive) { + locationPathname = locationPathname.toLowerCase(); + toPathname = toPathname.toLowerCase(); + } + + let isActive = locationPathname === toPathname; + if (!isActive && !end) { + // This is the behavior that is different from the original NavLink + isActive = locationPathname.startsWith(`${toPathname}/`); + } + + const ariaCurrent = isActive ? ariaCurrentProp : undefined; + + return ( + + ); +}); + export const SidebarItem = forwardRef((props, ref) => { const { icon: Icon, @@ -211,7 +264,7 @@ export const SidebarItem = forwardRef((props, ref) => { } return ( - ((props, ref) => { {...navLinkProps} > {content} - + ); }); diff --git a/packages/core-components/src/layout/SignInPage/SignInPage.tsx b/packages/core-components/src/layout/SignInPage/SignInPage.tsx index 51376511e2..dcaad83c72 100644 --- a/packages/core-components/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core-components/src/layout/SignInPage/SignInPage.tsx @@ -130,7 +130,9 @@ export const SingleSignInPage = ({ userId: identity!.id, profile: profile!, getIdToken: () => { - return authApi.getBackstageIdentity().then(i => i!.token); + return authApi + .getBackstageIdentity() + .then(i => i!.token ?? i!.idToken); }, signOut: async () => { await authApi.signOut(); diff --git a/packages/core-components/src/layout/SignInPage/auth0Provider.tsx b/packages/core-components/src/layout/SignInPage/auth0Provider.tsx index 2f5c8b5edb..518304d58c 100644 --- a/packages/core-components/src/layout/SignInPage/auth0Provider.tsx +++ b/packages/core-components/src/layout/SignInPage/auth0Provider.tsx @@ -40,7 +40,7 @@ const Component: ProviderComponent = ({ onResult }) => { userId: identity!.id, profile: profile!, getIdToken: () => - auth0AuthApi.getBackstageIdentity().then(i => i!.token), + auth0AuthApi.getBackstageIdentity().then(i => i!.token ?? i!.idToken), signOut: async () => { await auth0AuthApi.signOut(); }, @@ -82,7 +82,8 @@ const loader: ProviderLoader = async apis => { return { userId: identity.id, profile: profile!, - getIdToken: () => auth0AuthApi.getBackstageIdentity().then(i => i!.token), + getIdToken: () => + auth0AuthApi.getBackstageIdentity().then(i => i!.token ?? i!.idToken), signOut: async () => { await auth0AuthApi.signOut(); }, diff --git a/packages/core-components/src/layout/SignInPage/commonProvider.tsx b/packages/core-components/src/layout/SignInPage/commonProvider.tsx index 6f8339bcf3..0c5d195b1d 100644 --- a/packages/core-components/src/layout/SignInPage/commonProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/commonProvider.tsx @@ -86,7 +86,8 @@ const loader: ProviderLoader = async (apis, apiRef) => { return { userId: identity.id, profile: profile!, - getIdToken: () => authApi.getBackstageIdentity().then(i => i!.token), + getIdToken: () => + authApi.getBackstageIdentity().then(i => i!.token ?? i!.idToken), signOut: async () => { await authApi.signOut(); }, diff --git a/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx b/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx index 41b39984a5..c9f2a477ef 100644 --- a/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx +++ b/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx @@ -98,6 +98,7 @@ const TabbedCard = ({ {title && } ({ margin: theme.spacing(0, 2, 0, 0), padding: theme.spacing(0.5, 0, 0.5, 0), textTransform: 'none', + '&:hover': { + opacity: 1, + backgroundColor: 'transparent', + color: theme.palette.text.primary, + }, }, selected: { fontWeight: 'bold', diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index cd3b2a9a59..5ea46a334e 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/core-plugin-api +## 0.1.5 + +### Patch Changes + +- c4d8ff963: Switched frontend identity code to use `token` instead of the deprecated `idToken` field +- Updated dependencies + - @backstage/config@0.1.6 + ## 0.1.4 ### Patch Changes diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index da9fec915c..b87e4511d2 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -58,7 +58,7 @@ export type ApiFactory< Impl extends Api, Deps extends { [name in string]: unknown; - } + }, > = { api: ApiRef; deps: TypesToApiRefs; @@ -86,7 +86,7 @@ export type ApiRef = { export type ApiRefsToTypes< T extends { [key in string]: ApiRef; - } + }, > = { [key in keyof T]: ApiRefType; }; @@ -216,7 +216,7 @@ export type BackstageIdentityApi = { // @public (undocumented) export type BackstagePlugin< Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {} + ExternalRoutes extends AnyExternalRoutes = {}, > = { getId(): string; output(): PluginOutput[]; @@ -253,7 +253,7 @@ export function createApiFactory< Impl extends Api, Deps extends { [name in string]: unknown; - } + }, >(factory: ApiFactory): ApiFactory; // @public (undocumented) @@ -272,7 +272,7 @@ export function createApiRef(config: ApiRefConfig): ApiRef; // // @public (undocumented) export function createComponentExtension< - T extends (props: any) => JSX.Element | null + T extends (props: any) => JSX.Element | null, >(options: { component: ComponentLoader }): Extension; // Warning: (ae-forgotten-export) The symbol "OptionalParams" needs to be exported by the entry point index.d.ts @@ -284,7 +284,7 @@ export function createExternalRouteRef< [param in ParamKey]: string; }, Optional extends boolean = false, - ParamKey extends string = never + ParamKey extends string = never, >(options: { id: string; params?: ParamKey[]; @@ -296,7 +296,7 @@ export function createExternalRouteRef< // @public (undocumented) export function createPlugin< Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {} + ExternalRoutes extends AnyExternalRoutes = {}, >( config: PluginConfig, ): BackstagePlugin; @@ -305,7 +305,7 @@ export function createPlugin< // // @public (undocumented) export function createReactExtension< - T extends (props: any) => JSX.Element | null + T extends (props: any) => JSX.Element | null, >(options: { component: ComponentLoader; data?: Record; @@ -315,7 +315,7 @@ export function createReactExtension< // // @public (undocumented) export function createRoutableExtension< - T extends (props: any) => JSX.Element | null + T extends (props: any) => JSX.Element | null, >(options: { component: () => Promise; mountPoint: RouteRef }): Extension; // Warning: (ae-missing-release-tag) "createRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -325,7 +325,7 @@ export function createRouteRef< Params extends { [param in ParamKey]: string; }, - ParamKey extends string = never + ParamKey extends string = never, >(config: { id?: string; params?: ParamKey[]; @@ -342,7 +342,7 @@ export function createRouteRef< // @public (undocumented) export function createSubRouteRef< Path extends string, - ParentParams extends AnyParams = never + ParentParams extends AnyParams = never, >(config: { id: string; path: Path; @@ -369,7 +369,7 @@ export interface ElementCollection { getElements< Props extends { [name: string]: unknown; - } + }, >(): Array>; selectByComponentData(query: { key: string; @@ -421,7 +421,7 @@ export type Extension = { // @public (undocumented) export type ExternalRouteRef< Params extends AnyParams = any, - Optional extends boolean = any + Optional extends boolean = any, > = { readonly [routeRefType]: 'external'; params: ParamKeys; @@ -667,7 +667,7 @@ export type PendingAuthRequest = { // @public (undocumented) export type PluginConfig< Routes extends AnyRoutes, - ExternalRoutes extends AnyExternalRoutes + ExternalRoutes extends AnyExternalRoutes, > = { id: string; apis?: Iterable; @@ -885,9 +885,7 @@ export function useRouteRefParams( // Warning: (ae-missing-release-tag) "withApis" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function withApis( - apis: TypesToApiRefs, -):

    ( +export function withApis(apis: TypesToApiRefs):

    ( WrappedComponent: React_2.ComponentType

    , ) => { (props: React_2.PropsWithChildren>): JSX.Element; diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 374c158d57..a0057c1b0c 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "0.1.4", + "version": "0.1.5", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.3", + "@backstage/config": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", - "@types/react": "^16.9", + "@types/react": "*", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", @@ -41,8 +41,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.6", - "@backstage/core-app-api": "^0.1.6", + "@backstage/cli": "^0.7.7", + "@backstage/core-app-api": "^0.1.7", "@backstage/test-utils": "^0.1.16", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/core-plugin-api/src/apis/system/helpers.ts b/packages/core-plugin-api/src/apis/system/helpers.ts index 8e84dd6c09..547d583482 100644 --- a/packages/core-plugin-api/src/apis/system/helpers.ts +++ b/packages/core-plugin-api/src/apis/system/helpers.ts @@ -24,7 +24,7 @@ import { ApiRef, ApiFactory, TypesToApiRefs } from './types'; export function createApiFactory< Api, Impl extends Api, - Deps extends { [name in string]: unknown } + Deps extends { [name in string]: unknown }, >(factory: ApiFactory): ApiFactory; export function createApiFactory( api: ApiRef, @@ -33,7 +33,7 @@ export function createApiFactory( export function createApiFactory< Api, Impl extends Api, - Deps extends { [name in string]: unknown } + Deps extends { [name in string]: unknown }, >( factory: ApiFactory | ApiRef, instance?: Impl, diff --git a/packages/core-plugin-api/src/apis/system/types.ts b/packages/core-plugin-api/src/apis/system/types.ts index e805b6f255..a97d29fdaa 100644 --- a/packages/core-plugin-api/src/apis/system/types.ts +++ b/packages/core-plugin-api/src/apis/system/types.ts @@ -36,7 +36,7 @@ export type ApiHolder = { export type ApiFactory< Api, Impl extends Api, - Deps extends { [name in string]: unknown } + Deps extends { [name in string]: unknown }, > = { api: ApiRef; deps: TypesToApiRefs; diff --git a/packages/core-plugin-api/src/app/useApp.tsx b/packages/core-plugin-api/src/app/useApp.tsx index 1d17fb2d0f..aa323db4e2 100644 --- a/packages/core-plugin-api/src/app/useApp.tsx +++ b/packages/core-plugin-api/src/app/useApp.tsx @@ -18,9 +18,8 @@ import { useVersionedContext } from '../lib/versionedValues'; import { AppContext as AppContextV1 } from './types'; export const useApp = (): AppContextV1 => { - const versionedContext = useVersionedContext<{ 1: AppContextV1 }>( - 'app-context', - ); + const versionedContext = + useVersionedContext<{ 1: AppContextV1 }>('app-context'); const appContext = versionedContext.atVersion(1); if (!appContext) { throw new Error('AppContext v1 not available'); diff --git a/packages/core-plugin-api/src/extensions/extensions.tsx b/packages/core-plugin-api/src/extensions/extensions.tsx index 5142dccc55..73b927afc8 100644 --- a/packages/core-plugin-api/src/extensions/extensions.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.tsx @@ -33,7 +33,7 @@ type ComponentLoader = // ComponentType inserts children as an optional prop whether the inner component accepts it or not, // making it impossible to make the usage of children type safe. export function createRoutableExtension< - T extends (props: any) => JSX.Element | null + T extends (props: any) => JSX.Element | null, >(options: { component: () => Promise; mountPoint: RouteRef; @@ -91,7 +91,7 @@ export function createRoutableExtension< // ComponentType inserts children as an optional prop whether the inner component accepts it or not, // making it impossible to make the usage of children type safe. export function createComponentExtension< - T extends (props: any) => JSX.Element | null + T extends (props: any) => JSX.Element | null, >(options: { component: ComponentLoader }): Extension { const { component } = options; return createReactExtension({ component }); @@ -101,7 +101,7 @@ export function createComponentExtension< // ComponentType inserts children as an optional prop whether the inner component accepts it or not, // making it impossible to make the usage of children type safe. export function createReactExtension< - T extends (props: any) => JSX.Element | null + T extends (props: any) => JSX.Element | null, >(options: { component: ComponentLoader; data?: Record; @@ -111,9 +111,9 @@ export function createReactExtension< let Component: T; if ('lazy' in options.component) { const lazyLoader = options.component.lazy; - Component = (lazy(() => + Component = lazy(() => lazyLoader().then(component => ({ default: component })), - ) as unknown) as T; + ) as unknown as T; } else { Component = options.component.sync; } diff --git a/packages/core-plugin-api/src/lib/versionedValues.ts b/packages/core-plugin-api/src/lib/versionedValues.ts index a7b7d8e3b7..7686842ecd 100644 --- a/packages/core-plugin-api/src/lib/versionedValues.ts +++ b/packages/core-plugin-api/src/lib/versionedValues.ts @@ -32,7 +32,7 @@ export type VersionedValue = { * Creates a container for a map of versioned values that implements VersionedValue. */ export function createVersionedValueMap< - Versions extends { [version: number]: any } + Versions extends { [version: number]: any }, >(versions: Versions): VersionedValue { Object.freeze(versions); return { @@ -43,7 +43,7 @@ export function createVersionedValueMap< } export function useVersionedContext< - Versions extends { [version in number]: any } + Versions extends { [version in number]: any }, >(key: string): VersionedValue { const versionedValue = useContext( getGlobalSingleton>>(key), diff --git a/packages/core-plugin-api/src/plugin/Plugin.tsx b/packages/core-plugin-api/src/plugin/Plugin.tsx index abf4bfa35c..53a53b76d0 100644 --- a/packages/core-plugin-api/src/plugin/Plugin.tsx +++ b/packages/core-plugin-api/src/plugin/Plugin.tsx @@ -26,8 +26,9 @@ import { AnyApiFactory } from '../apis'; export class PluginImpl< Routes extends AnyRoutes, - ExternalRoutes extends AnyExternalRoutes -> implements BackstagePlugin { + ExternalRoutes extends AnyExternalRoutes, +> implements BackstagePlugin +{ private storedOutput?: PluginOutput[]; constructor(private readonly config: PluginConfig) {} @@ -81,7 +82,7 @@ export class PluginImpl< export function createPlugin< Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {} + ExternalRoutes extends AnyExternalRoutes = {}, >( config: PluginConfig, ): BackstagePlugin { diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts index 23ff4594a5..b3e1043efd 100644 --- a/packages/core-plugin-api/src/plugin/types.ts +++ b/packages/core-plugin-api/src/plugin/types.ts @@ -42,7 +42,7 @@ export type AnyExternalRoutes = { [name: string]: ExternalRouteRef }; export type BackstagePlugin< Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {} + ExternalRoutes extends AnyExternalRoutes = {}, > = { getId(): string; output(): PluginOutput[]; @@ -54,7 +54,7 @@ export type BackstagePlugin< export type PluginConfig< Routes extends AnyRoutes, - ExternalRoutes extends AnyExternalRoutes + ExternalRoutes extends AnyExternalRoutes, > = { id: string; apis?: Iterable; diff --git a/packages/core-plugin-api/src/routing/ExternalRouteRef.ts b/packages/core-plugin-api/src/routing/ExternalRouteRef.ts index 348c78220f..401e34773c 100644 --- a/packages/core-plugin-api/src/routing/ExternalRouteRef.ts +++ b/packages/core-plugin-api/src/routing/ExternalRouteRef.ts @@ -24,8 +24,9 @@ import { export class ExternalRouteRefImpl< Params extends AnyParams, - Optional extends boolean -> implements ExternalRouteRef { + Optional extends boolean, +> implements ExternalRouteRef +{ readonly [routeRefType] = 'external'; constructor( @@ -42,7 +43,7 @@ export class ExternalRouteRefImpl< export function createExternalRouteRef< Params extends { [param in ParamKey]: string }, Optional extends boolean = false, - ParamKey extends string = never + ParamKey extends string = never, >(options: { /** * An identifier for this route, used to identify it in error messages diff --git a/packages/core-plugin-api/src/routing/RouteRef.ts b/packages/core-plugin-api/src/routing/RouteRef.ts index 0239b57c25..31dd9ef518 100644 --- a/packages/core-plugin-api/src/routing/RouteRef.ts +++ b/packages/core-plugin-api/src/routing/RouteRef.ts @@ -32,7 +32,8 @@ export type RouteRefConfig = { }; export class RouteRefImpl - implements RouteRef { + implements RouteRef +{ readonly [routeRefType] = 'absolute'; constructor( @@ -70,7 +71,7 @@ export function createRouteRef< // ParamKey is here to make sure the Params type properly has its keys narrowed down // to only the elements of params. Defaulting to never makes sure we end up with // Param = {} if the params array is empty. - ParamKey extends string = never + ParamKey extends string = never, >(config: { /** The id of the route ref, used to identify it when printed */ id?: string; diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.ts b/packages/core-plugin-api/src/routing/SubRouteRef.ts index f7e5cf0bc5..6b329dfd42 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.ts @@ -27,7 +27,8 @@ import { const PARAM_PATTERN = /^\w+$/; export class SubRouteRefImpl - implements SubRouteRef { + implements SubRouteRef +{ readonly [routeRefType] = 'sub'; constructor( @@ -55,7 +56,7 @@ type PathParams = { [name in ParamNames]: string }; */ type MergeParams< P1 extends { [param in string]: string }, - P2 extends AnyParams + P2 extends AnyParams, > = (P1[keyof P1] extends never ? {} : P1) & (P2 extends undefined ? {} : P2); /** @@ -64,14 +65,14 @@ type MergeParams< */ type MakeSubRouteRef< Params extends { [param in string]: string }, - ParentParams extends AnyParams + ParentParams extends AnyParams, > = keyof Params & keyof ParentParams extends never ? SubRouteRef>> : never; export function createSubRouteRef< Path extends string, - ParentParams extends AnyParams = never + ParentParams extends AnyParams = never, >(config: { id: string; path: Path; diff --git a/packages/core-plugin-api/src/routing/types.ts b/packages/core-plugin-api/src/routing/types.ts index 9ea1e3ff0d..5afcc72265 100644 --- a/packages/core-plugin-api/src/routing/types.ts +++ b/packages/core-plugin-api/src/routing/types.ts @@ -21,9 +21,8 @@ export type AnyParams = { [param in string]: string } | undefined; export type ParamKeys = keyof Params extends never ? [] : (keyof Params)[]; -export type OptionalParams< - Params extends { [param in string]: string } -> = Params[keyof Params] extends never ? undefined : Params; +export type OptionalParams = + Params[keyof Params] extends never ? undefined : Params; // The extra TS magic here is to require a single params argument if the RouteRef // had at least one param defined, but require 0 arguments if there are no params defined. @@ -65,7 +64,7 @@ export type SubRouteRef = { export type ExternalRouteRef< Params extends AnyParams = any, - Optional extends boolean = any + Optional extends boolean = any, > = { readonly [routeRefType]: 'external'; diff --git a/packages/core-plugin-api/src/routing/useRouteRef.tsx b/packages/core-plugin-api/src/routing/useRouteRef.tsx index 4447759b99..ed261489a4 100644 --- a/packages/core-plugin-api/src/routing/useRouteRef.tsx +++ b/packages/core-plugin-api/src/routing/useRouteRef.tsx @@ -48,9 +48,8 @@ export function useRouteRef( | ExternalRouteRef, ): RouteFunc | undefined { const sourceLocation = useLocation(); - const versionedContext = useVersionedContext<{ 1: RouteResolver }>( - 'routing-context', - ); + const versionedContext = + useVersionedContext<{ 1: RouteResolver }>('routing-context'); const resolver = versionedContext.atVersion(1); const routeFunc = useMemo( () => resolver && resolver.resolve(routeRef, sourceLocation), diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index ba2fbeebfc..318c508211 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/create-app +## 0.3.34 + +### Patch Changes + +- c189c5da5: fix typo in the comments of EntityPage component +- 48ea3d25b: The recommended value for a `backstage.io/techdocs-ref` annotation is now + `dir:.`, indicating "documentation source files are located in the same + directory relative to the catalog entity." Note that `url:` values + are still supported. +- 98dda80b4: Update `techdocs.generators` with the latest `techdocs.generator` config in `app-config.yaml`. See + https://backstage.io/docs/features/techdocs/configuration for reference and relevant PR + https://github.com/backstage/backstage/pull/6071/files for the changes. + ## 0.3.33 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 4a5d7b18f1..130c69603a 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.3.33", + "version": "0.3.34", "private": false, "publishConfig": { "access": "public" @@ -39,7 +39,6 @@ "devDependencies": { "@types/fs-extra": "^9.0.1", "@types/inquirer": "^7.3.1", - "@types/react-dev-utils": "^9.0.4", "@types/recursive-readdir": "^2.2.0", "ts-node": "^10.0.0" }, diff --git a/packages/create-app/templates/default-app/.prettierignore b/packages/create-app/templates/default-app/.prettierignore new file mode 100644 index 0000000000..dfb0f1cd11 --- /dev/null +++ b/packages/create-app/templates/default-app/.prettierignore @@ -0,0 +1,4 @@ +dist +dist-types +coverage +.vscode diff --git a/packages/create-app/templates/default-app/README.md b/packages/create-app/templates/default-app/README.md index 07cf6fd136..8c7c4373fe 100644 --- a/packages/create-app/templates/default-app/README.md +++ b/packages/create-app/templates/default-app/README.md @@ -7,4 +7,4 @@ To start the app, run: ```sh yarn install yarn dev -``` \ No newline at end of file +``` diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index dc296e5e62..9c35c58c33 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -32,9 +32,9 @@ backend: user: ${POSTGRES_USER} password: ${POSTGRES_PASSWORD} # https://node-postgres.com/features/ssl - #ssl: require # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) - #ca: # if you have a CA file and want to verify it you can uncomment this section - # $file: /ca/server.crt + # ssl: require # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) + # ca: # if you have a CA file and want to verify it you can uncomment this section + # $file: /ca/server.crt {{/if}} cache: store: memory diff --git a/packages/create-app/templates/default-app/catalog-info.yaml.hbs b/packages/create-app/templates/default-app/catalog-info.yaml.hbs index 265864e235..e01ce8f756 100644 --- a/packages/create-app/templates/default-app/catalog-info.yaml.hbs +++ b/packages/create-app/templates/default-app/catalog-info.yaml.hbs @@ -5,8 +5,8 @@ metadata: description: An example of a Backstage application. # Example for optional annotations # annotations: - # github.com/project-slug: backstage/backstage - # backstage.io/techdocs-ref: dir:. + # github.com/project-slug: backstage/backstage + # backstage.io/techdocs-ref: dir:. spec: type: website owner: john@example.com diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index 7ae86c0fbc..3f913102cb 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -19,6 +19,7 @@ "test:all": "lerna run test -- --coverage", "lint": "lerna run lint --since origin/master --", "lint:all": "lerna run lint --", + "prettier:check": "prettier --check .", "create-plugin": "backstage-cli create-plugin --scope internal --no-private", "remove-plugin": "backstage-cli remove-plugin" }, @@ -37,7 +38,7 @@ "@spotify/prettier-config": "^7.0.0", "concurrently": "^6.0.0", "lerna": "^4.0.0", - "prettier": "^1.19.1" + "prettier": "^2.3.2" }, "prettier": "@spotify/prettier-config", "lint-staged": { diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index 219f503aa0..c443b45dc7 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -38,7 +38,7 @@ "@testing-library/user-event": "^12.0.7", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", - "@types/react-dom": "^16.9.8", + "@types/react-dom": "*", "cross-env": "^7.0.0", "cypress": "^7.3.0", "eslint-plugin-cypress": "^2.10.3", diff --git a/packages/create-app/templates/default-app/packages/app/public/index.html b/packages/create-app/templates/default-app/packages/app/public/index.html index 5653173480..b93896c6da 100644 --- a/packages/create-app/templates/default-app/packages/app/public/index.html +++ b/packages/create-app/templates/default-app/packages/app/public/index.html @@ -65,7 +65,7 @@ <% } %> - +