diff --git a/.changeset/bright-buttons-rescue.md b/.changeset/bright-buttons-rescue.md deleted file mode 100644 index 71a6b76fa2..0000000000 --- a/.changeset/bright-buttons-rescue.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@backstage/plugin-auth-backend': minor ---- - -**BREAKING** Added `tokenManager` as a required property for the auth-backend `createRouter` function. This dependency is used to issue server tokens that are used by the `CatalogIdentityClient` when looking up users and their group membership during authentication. - -These changes are **required** to `packages/backend/src/plugins/auth.ts`: - -```diff -export default async function createPlugin({ - logger, - database, - config, - discovery, -+ tokenManager, -}: PluginEnvironment): Promise { - return await createRouter({ - logger, - config, - database, - discovery, -+ tokenManager, - }); -} -``` - -**BREAKING** The `CatalogIdentityClient` constructor now expects a `TokenManager` instead of a `TokenIssuer`. The `TokenManager` interface is used to generate a server token when [resolving a user's identity and membership through the catalog](https://backstage.io/docs/auth/identity-resolver). Using server tokens for these requests allows the auth-backend to bypass authorization checks when permissions are enabled for Backstage. This change will break apps that rely on the user tokens that were previously used by the client. Refer to the ["Backend-to-backend Authentication" tutorial](https://backstage.io/docs/tutorials/backend-to-backend-auth) for more information on server token usage. diff --git a/.changeset/dependabot-2f11dff.md b/.changeset/dependabot-2f11dff.md deleted file mode 100644 index f3397cad0c..0000000000 --- a/.changeset/dependabot-2f11dff.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-rollbar-backend': patch ---- - -chore(deps): bump `camelcase-keys` from 6.2.2 to 7.0.1 diff --git a/.changeset/dependabot-9ec400d.md b/.changeset/dependabot-9ec400d.md deleted file mode 100644 index 1fd5a85477..0000000000 --- a/.changeset/dependabot-9ec400d.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -chore(deps): bump `passport` from 0.4.1 to 0.5.2 diff --git a/.changeset/dependabot-f969614.md b/.changeset/dependabot-f969614.md deleted file mode 100644 index c3c8890ec9..0000000000 --- a/.changeset/dependabot-f969614.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -chore(deps): bump `prom-client` from 13.2.0 to 14.0.1 diff --git a/.changeset/early-cooks-brake.md b/.changeset/early-cooks-brake.md deleted file mode 100644 index 3037d71ff4..0000000000 --- a/.changeset/early-cooks-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Apply the fix from `0.4.16`, which is part of the `v0.65.1` release of Backstage. diff --git a/.changeset/flat-cars-begin.md b/.changeset/flat-cars-begin.md deleted file mode 100644 index 3a8837dc0e..0000000000 --- a/.changeset/flat-cars-begin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Change subtitle of Header style to use palette.bursts.fontColor diff --git a/.changeset/gold-queens-clap.md b/.changeset/gold-queens-clap.md deleted file mode 100644 index 252fbb1626..0000000000 --- a/.changeset/gold-queens-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Fix an issue where changes related to the `MobileSidebar` prevented scrolling pages. Additionally improve the menu of the `MobileSidebar` to not overlay the `BottomNavigation`. diff --git a/.changeset/grumpy-teachers-remain.md b/.changeset/grumpy-teachers-remain.md deleted file mode 100644 index 60653b084f..0000000000 --- a/.changeset/grumpy-teachers-remain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Migrate from deprecated package @octokit/rest to octokit diff --git a/.changeset/nasty-pets-glow.md b/.changeset/nasty-pets-glow.md deleted file mode 100644 index e630736261..0000000000 --- a/.changeset/nasty-pets-glow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Added `relations.memberof` filter to the catalog api call in `MemberListCard` to avoid fetching all the User entity kinds from catalog-backend. diff --git a/.changeset/neat-mangos-study.md b/.changeset/neat-mangos-study.md deleted file mode 100644 index 7daefcadac..0000000000 --- a/.changeset/neat-mangos-study.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Update backend-to-backend auth link in configuration file comment diff --git a/.changeset/patched.json b/.changeset/patched.json index f2a4688500..1029c8f106 100644 --- a/.changeset/patched.json +++ b/.changeset/patched.json @@ -1,5 +1,3 @@ { - "currentReleaseVersion": { - "@backstage/create-app": "0.4.16" - } + "currentReleaseVersion": {} } diff --git a/.changeset/pre.json b/.changeset/pre.json deleted file mode 100644 index f9db6af257..0000000000 --- a/.changeset/pre.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "mode": "pre", - "tag": "next", - "initialVersions": { - "example-app": "0.2.62", - "@backstage/app-defaults": "0.1.5", - "example-backend": "0.2.62", - "@backstage/backend-common": "0.10.5", - "@backstage/backend-tasks": "0.1.4", - "@backstage/backend-test-utils": "0.1.15", - "@backstage/catalog-client": "0.5.5", - "@backstage/catalog-model": "0.9.10", - "@backstage/cli": "0.13.0", - "@backstage/cli-common": "0.1.6", - "@backstage/codemods": "0.1.31", - "@backstage/config": "0.1.13", - "@backstage/config-loader": "0.9.3", - "@backstage/core-app-api": "0.5.1", - "@backstage/core-components": "0.8.6", - "@backstage/core-plugin-api": "0.6.0", - "@backstage/create-app": "0.4.15", - "@backstage/dev-utils": "0.2.19", - "e2e-test": "0.2.0", - "embedded-techdocs-app": "0.2.61", - "@backstage/errors": "0.2.0", - "@backstage/integration": "0.7.2", - "@backstage/integration-react": "0.1.19", - "@backstage/search-common": "0.2.2", - "@techdocs/cli": "0.8.11", - "@backstage/techdocs-common": "0.11.5", - "@backstage/test-utils": "0.2.3", - "@backstage/theme": "0.2.14", - "@backstage/types": "0.1.1", - "@backstage/version-bridge": "0.1.1", - "@backstage/plugin-airbrake": "0.1.1", - "@backstage/plugin-allure": "0.1.12", - "@backstage/plugin-analytics-module-ga": "0.1.7", - "@backstage/plugin-apache-airflow": "0.1.4", - "@backstage/plugin-api-docs": "0.7.0", - "@backstage/plugin-app-backend": "0.3.22", - "@backstage/plugin-auth-backend": "0.8.0", - "@backstage/plugin-azure-devops": "0.1.12", - "@backstage/plugin-azure-devops-backend": "0.3.1", - "@backstage/plugin-azure-devops-common": "0.2.0", - "@backstage/plugin-badges": "0.2.20", - "@backstage/plugin-badges-backend": "0.1.16", - "@backstage/plugin-bazaar": "0.1.11", - "@backstage/plugin-bazaar-backend": "0.1.7", - "@backstage/plugin-bitrise": "0.1.23", - "@backstage/plugin-catalog": "0.7.10", - "@backstage/plugin-catalog-backend": "0.21.1", - "@backstage/plugin-catalog-backend-module-ldap": "0.3.10", - "@backstage/plugin-catalog-backend-module-msgraph": "0.2.13", - "@backstage/plugin-catalog-common": "0.1.1", - "@backstage/plugin-catalog-graph": "0.2.8", - "@backstage/plugin-catalog-graphql": "0.3.1", - "@backstage/plugin-catalog-import": "0.7.10", - "@backstage/plugin-catalog-react": "0.6.12", - "@backstage/plugin-circleci": "0.2.35", - "@backstage/plugin-cloudbuild": "0.2.33", - "@backstage/plugin-code-coverage": "0.1.23", - "@backstage/plugin-code-coverage-backend": "0.1.20", - "@backstage/plugin-config-schema": "0.1.19", - "@backstage/plugin-cost-insights": "0.11.18", - "@backstage/plugin-explore": "0.3.27", - "@backstage/plugin-explore-react": "0.0.11", - "@backstage/plugin-firehydrant": "0.1.13", - "@backstage/plugin-fossa": "0.2.28", - "@backstage/plugin-gcp-projects": "0.3.15", - "@backstage/plugin-git-release-manager": "0.3.9", - "@backstage/plugin-github-actions": "0.4.33", - "@backstage/plugin-github-deployments": "0.1.27", - "@backstage/plugin-gitops-profiles": "0.3.14", - "@backstage/plugin-gocd": "0.1.2", - "@backstage/plugin-graphiql": "0.2.28", - "@backstage/plugin-graphql-backend": "0.1.12", - "@backstage/plugin-home": "0.4.12", - "@backstage/plugin-ilert": "0.1.22", - "@backstage/plugin-jenkins": "0.5.18", - "@backstage/plugin-jenkins-backend": "0.1.11", - "@backstage/plugin-kafka": "0.2.26", - "@backstage/plugin-kafka-backend": "0.2.15", - "@backstage/plugin-kubernetes": "0.5.5", - "@backstage/plugin-kubernetes-backend": "0.4.5", - "@backstage/plugin-kubernetes-common": "0.2.2", - "@backstage/plugin-lighthouse": "0.2.35", - "@backstage/plugin-newrelic": "0.3.14", - "@backstage/plugin-newrelic-dashboard": "0.1.4", - "@backstage/plugin-org": "0.4.0", - "@backstage/plugin-pagerduty": "0.3.23", - "@backstage/plugin-permission-backend": "0.4.1", - "@backstage/plugin-permission-common": "0.4.0", - "@backstage/plugin-permission-node": "0.4.1", - "@backstage/plugin-permission-react": "0.3.0", - "@backstage/plugin-proxy-backend": "0.2.16", - "@backstage/plugin-rollbar": "0.3.24", - "@backstage/plugin-rollbar-backend": "0.1.19", - "@backstage/plugin-scaffolder": "0.12.0", - "@backstage/plugin-scaffolder-backend": "0.15.22", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.1.9", - "@backstage/plugin-scaffolder-backend-module-rails": "0.2.4", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.1.3", - "@backstage/plugin-scaffolder-common": "0.1.3", - "@backstage/plugin-search": "0.6.0", - "@backstage/plugin-search-backend": "0.4.0", - "@backstage/plugin-search-backend-module-elasticsearch": "0.0.8", - "@backstage/plugin-search-backend-module-pg": "0.2.4", - "@backstage/plugin-search-backend-node": "0.4.5", - "@backstage/plugin-sentry": "0.3.34", - "@backstage/plugin-shortcuts": "0.1.20", - "@backstage/plugin-sonarqube": "0.2.14", - "@backstage/plugin-splunk-on-call": "0.3.20", - "@backstage/plugin-tech-insights": "0.1.6", - "@backstage/plugin-tech-insights-backend": "0.2.2", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.6", - "@backstage/plugin-tech-insights-common": "0.2.1", - "@backstage/plugin-tech-insights-node": "0.2.0", - "@backstage/plugin-tech-radar": "0.5.3", - "@backstage/plugin-techdocs": "0.13.1", - "@backstage/plugin-techdocs-backend": "0.13.1", - "@backstage/plugin-todo": "0.1.20", - "@backstage/plugin-todo-backend": "0.1.19", - "@backstage/plugin-user-settings": "0.3.17", - "@backstage/plugin-xcmetrics": "0.2.16" - }, - "changesets": [] -} diff --git a/.changeset/purple-steaks-design.md b/.changeset/purple-steaks-design.md deleted file mode 100644 index 4857c39c73..0000000000 --- a/.changeset/purple-steaks-design.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Adds missing `/catalog-graph` route to ``. - -To fix this problem for a recently created app please update your `app/src/App.tsx` - -```diff -+ import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; - - ... omitted ... - - - } /> -+ } /> - -``` diff --git a/.changeset/quick-jars-wait.md b/.changeset/quick-jars-wait.md deleted file mode 100644 index bbf6b42a89..0000000000 --- a/.changeset/quick-jars-wait.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@backstage/plugin-proxy-backend': patch ---- - -Adds a new option `skipInvalidTargets` for the proxy `createRouter` which allows the proxy backend to be started with an invalid proxy configuration. If configured, it will simply skip the failed proxy and mount the other valid proxies. - -To configure it to pass by failing proxies: - -``` -const router = await createRouter({ - config, - logger, - discovery, - skipInvalidProxies: true, -}); -``` - -If you would like it to fail if a proxy is configured badly: - -``` -const router = await createRouter({ - config, - logger, - discovery, -}); -``` diff --git a/.changeset/sharp-dragons-divide.md b/.changeset/sharp-dragons-divide.md deleted file mode 100644 index 398d7fb38a..0000000000 --- a/.changeset/sharp-dragons-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@techdocs/cli': patch ---- - -Use a local file dependency for embedded-techdocs-app, to ensure that it's always pulled out of the workspace diff --git a/.changeset/sour-chairs-double.md b/.changeset/sour-chairs-double.md deleted file mode 100644 index 4f481e20e7..0000000000 --- a/.changeset/sour-chairs-double.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added `tokenManager` as a required property for the auth-backend `createRouter` function. This dependency is used to issue server tokens that are used by the `CatalogIdentityClient` when looking up users and their group membership during authentication. - -These changes are **required** to `packages/backend/src/plugins/auth.ts`: - -```diff -export default async function createPlugin({ - logger, - database, - config, - discovery, -+ tokenManager, -}: PluginEnvironment): Promise { - return await createRouter({ - logger, - config, - database, - discovery, -+ tokenManager, - }); -} -``` diff --git a/.changeset/tall-rats-lie.md b/.changeset/tall-rats-lie.md deleted file mode 100644 index ed76970df4..0000000000 --- a/.changeset/tall-rats-lie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-pagerduty': patch ---- - -Fix change events tab error when change events exist diff --git a/.changeset/tame-ads-exercise.md b/.changeset/tame-ads-exercise.md deleted file mode 100644 index a2e11393a6..0000000000 --- a/.changeset/tame-ads-exercise.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -'@backstage/plugin-catalog-import': minor ---- - -Make filename, branch name and examples URLs used in catalog import customizable. - -Catalog backend ingestion loop can be already configured to fetch targets with custom catalog filename (other than `catalog-info.yaml`). It's now possible to customize said filename and branch name used in pull requests created by catalog import flow too. This allows organizations to further customize Backstage experience and to better reflect their branding. - -Filename (default: `catalog-info.yaml`) and branch name (default: `backstage-integration`) used in pull requests can be configured in `app-config.yaml` as follows: - -```yaml -// app-config.yaml - -catalog: - import: - entityFilename: anvil.yaml - pullRequestBranchName: anvil-integration -``` - -Following React components have also been updated to accept optional props for providing example entity and repository paths: - -```tsx - -``` - -```tsx - -``` diff --git a/.changeset/thirty-houses-juggle.md b/.changeset/thirty-houses-juggle.md deleted file mode 100644 index c54ab5526f..0000000000 --- a/.changeset/thirty-houses-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Pass authorization token to location service inside location api routes diff --git a/.changeset/tiny-buses-compete.md b/.changeset/tiny-buses-compete.md deleted file mode 100644 index 2cb8d1a87f..0000000000 --- a/.changeset/tiny-buses-compete.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-components': patch -'@backstage/plugin-scaffolder': patch ---- - -Make linkTarget configurable for MarkdownContent component diff --git a/.changeset/witty-lamps-laugh.md b/.changeset/witty-lamps-laugh.md deleted file mode 100644 index 1ca4f4e4dc..0000000000 --- a/.changeset/witty-lamps-laugh.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Switched the `app` dependency in the backend to use a file target rather than version. - -To apply this change to an existing app, make the following change to `packages/backend/package.json`: - -```diff - "dependencies": { -- "app": "0.0.0", -+ "app": "file:../app", -``` diff --git a/.changeset/witty-lizards-nail.md b/.changeset/witty-lizards-nail.md deleted file mode 100644 index 2ce3faa3f7..0000000000 --- a/.changeset/witty-lizards-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Log warning if unable to parse yarn.lock diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a587419d50..08057d5af6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -34,9 +34,9 @@ /tech-insights-backend-module-jsonfc @backstage/reviewers @xantier @iain-b /tech-insights-tech-insights-common @backstage/reviewers @xantier @iain-b /tech-insights-tech-insights-node @backstage/reviewers @xantier @iain-b -/packages/embedded-techdocs-app @backstage/techdocs-core /packages/search-common @backstage/techdocs-core /packages/techdocs-cli @backstage/techdocs-core +/packages/techdocs-cli-embedded-app @backstage/techdocs-core /packages/techdocs-common @backstage/techdocs-core /.changeset/cost-insights-* @backstage/reviewers @backstage/silver-lining /.changeset/search-* @backstage/techdocs-core diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 49c85e7e1c..8303510e77 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -28,6 +28,7 @@ Blackbox bool boolean builtins +callout Chai changeset changesets @@ -223,6 +224,7 @@ productional Protobuf proxying Proxying +pseudonymized pubsub pygments pymdownx @@ -314,6 +316,7 @@ unbreak Unconference unmanaged unregister +unregistering unregistration untracked upsert @@ -337,4 +340,4 @@ Zalando Zhou zoomable zsh -Alef \ No newline at end of file +Alef diff --git a/ADOPTERS.md b/ADOPTERS.md index b95e20757d..8772c27a88 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -85,3 +85,6 @@ | [Just Eat Takeaway](https://www.justeattakeaway.com) | [Kim Wilson](https://github.com/kwilson541) | Our developer portal which centralises applications, reduces cognitive load and provides teams insights. | | [Hopin](https://hopin.com) | [Vladimir Glafirov](https://github.com/vglafirov), [Chloe Lee](https://github.com/msfuko) | Developer portal to streamline the development practices. Integrated with service catalog, software templates, application monitoring, tech docs and plugins. | | [HBO Max](https://hbomax.com) | [@mdb](https://github.com/mdb), [@nesta219](https://github.com/nesta219), [@nmische](https://github.com/nmische), [@hbomark](https://github.com/hbomark) | Developer portal hosting service catalog and API documentation, as well as cloud infrastructure details, operational visibility tools, and a custom plugin for browsing notable platform change events, such as deployments and configuration updates. | +| [RCHLO](https://www.riachuelo.com.br) & [MIDWAY](https://www.midway.com.br) | [@marcosborges](https://github.com/marcosborges), [@defaultbr](https://github.com/defaultbr) | Self-Service Platform | +| [HP Inc](https://www.hp.com) | [Damon Kaswell](https://github.com/dekoding) | DevEx engagement hub (dev portal: docs, standards, Q&A) and extensive assets catalog (APIs, services, code, data, etc.) for the pan-HP internal developer community. +| [VMware](https://www.vmware.com) | [@mpriamo](https://github.com/mpriamo), [@krisapplegate](https://github.com/krisapplegate) | Part of [Tanzu Application Platform](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/index.html) offering; internal developer portal | diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 936c27c7cf..3bcef61021 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -36,6 +36,25 @@ To become a maintainer you need to demonstrate the following: If a maintainer is no longer interested or cannot perform the maintainer duties listed above, they should volunteer to be moved to emeritus status. In extreme cases this can also occur by a vote of the sponsors and maintainers per the voting process below. +# End User Sponsors + +## Role of a Backstage End User Sponsor + +- Provide support for Backstage by removing blockers, securing funding, providing advocacy, feedback, and ensuring project continuity and long term success. +- Assist Backstage maintainers in prioritizing upcoming roadmap items and planned work. +- Provide neutral mediation for any disputes that arise as part of the project. + +## Backstage End User Sponsor Membership + +The End User Sponsors group comprises at most 5 people. To be eligible for membership in the group, you or the company where you work you must: + +- Be responsible for and end user of a production Backstage deployment of non-trivial size +- Be active contributors to the open source project +- Be willing and able to attend regularly-scheduled End User Sponsor meetings +- Abide by [Backstage’s Code of Conduct](./CODE_OF_CONDUCT.md). + +Candidates for membership will be nominated by current Sponsor members or by Backstage maintainers. If there are more nominations than Sponsor seats remaining, existing sponsors shall vote on the candidates, and the candidates with the most votes will become Sponsors. Any ties will be broken by current Backstage sponsors. + # Reviewers The project also contains a team called [@backstage/reviewers](https://github.com/orgs/backstage/teams/reviewers). This is the team of people who are the fallback in [`CODEOWNERS`](./.github/CODEOWNERS). This team will typically contain the maintainers, and a small number of additional people who are permitted to approve and merge pull requests. The purpose of this group is to offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors. diff --git a/docs/features/software-catalog/api.md b/docs/features/software-catalog/api.md index 71e229651a..3221b9e46a 100644 --- a/docs/features/software-catalog/api.md +++ b/docs/features/software-catalog/api.md @@ -21,7 +21,7 @@ section below is `/entities`, and the catalog is located at `http://localhost:7007/api/catalog` during local development, the full URL would be `http://localhost:7007/api/catalog/entities`. The actual URL may vary from one organization to the other, especially in production, but is commonly your -`backend.baseUrl` in your app config, plus `/catalog` at the end. +`backend.baseUrl` in your app config, plus `/api/catalog` at the end. Some or all of the endpoints may accept or require an `Authorization` header with a `Bearer` token, which should then be the Backstage token returned by the diff --git a/docs/features/software-catalog/life-of-an-entity.md b/docs/features/software-catalog/life-of-an-entity.md index 9e8a3f0563..618923289e 100644 --- a/docs/features/software-catalog/life-of-an-entity.md +++ b/docs/features/software-catalog/life-of-an-entity.md @@ -48,9 +48,9 @@ The details of these processes are described below. ## Ingestion -Each catalog deployment has a number of entity providers installed. They are +Each catalog deployment has a number of _entity providers_ installed. They are responsible for fetching data from external authoritative sources in any way -that they see fit, to translate those into entity objects, and to notify the +that they see fit, translating those into entity objects, and notifying the database when those entities are added or removed. These are the _unprocessed entities_ that will be subject to later processing (see below), and they form the very basis of existence for entities. If there were no entity providers, no @@ -108,7 +108,7 @@ the processing loop should next try to process it. When the entity first appears, this timestamp is set to "now" - asking for it to be picked up as soon as possible. -Each catalog deployment has a number of processors installed. They are +Each catalog deployment has a number of _processors_ installed. They are responsible for receiving unprocessed entities that the catalog decided are due for processing, and then running that data through a number of processing stages, mutating the entity and emitting auxiliary data about it. When all of @@ -185,7 +185,28 @@ happen during ingestion or processing. ## Errors -> TODO: Describe how errors are exposed through entities +Errors during the ingestion and processing of entities can happen in a variety +of ways, and they may happen at a far later point in time than when they were +registered. For example, a registered file may get deleted in the remote system, +or the user may accidentally change the file contents in such a way that they +cannot be parsed successfully, etc. + +There are two main ways that these errors are surfaced. + +First, the catalog backend will produce detailed logs that should contain +sufficient information for a reader to find the causes for errors. Since these +logs are typically not easily found by end users, this can mainly be a useful +tool for Backstage operators who want to debug problems either with statically +registered entities that are under their control, or to help end users find +problems. + +Second, for most classes of errors, the entity itself will contain a status +field that describes the problem. The contents of this field is shown at the top +of your entity page in Backstage, if you have placed the corresponding error +callout component (`EntityProcessingErrorsPanel`) there. + +We are still working to improve the surfacing and observability around +processing loop errors. ## Orphaning @@ -206,17 +227,22 @@ either, it becomes _orphaned_. The end result is as follows: - The catalog page in Backstage for the child entity detects the new annotation and informs users about the orphan status. -Orphaning can occur in several different scenarios. One common cause is that the -end user edited a corresponding catalog catalog-info YAML file removing the -entity's entry. In the case of a `Location` parent entity, orphaning can happen -if removing the target line pointing to the file containing the child entity. -Another common cause is large batch processors such as the ones that crawl -through remote systems looking for entities, no longer finding something that it -used to find before. Maybe the data was moved, or deleted, in the remote system. -So for example when a person leaves the company an LDAP org discovery processor -might leave an orphaned `User` entity behind. Note that this only applies to -processors - ingestion that happens using entity providers work differently, -described below. +Orphaning can occur in several different scenarios. + +- If a catalog-info YAML file is moved from one place to another in the version + control system without updating the registration in the catalog, it will + effectively become orphaned "by" that registered location +- If the user edits a corresponding parent catalog-info YAML file removing the + entity's entry - for example in the case of a `Location` parent entity, + orphaning can happen if editing or removing the `target`/`targets` lines + pointing to the file containing the child entity. +- Another common cause is large batch processors such as the ones that crawl + through remote systems looking for entities, no longer finding something that + it used to find before. Maybe the data was moved, or deleted, in the remote + system. So for example when a person leaves the company an LDAP org discovery + processor might leave an orphaned `User` entity behind. Note that this only + applies to processors - ingestion that happens using entity providers work + differently, described below. > Note that removing a file, or accidentally corrupting a file so that it cannot > be read successfully, does _not_ lead to orphaning. Hard errors, including the @@ -239,8 +265,78 @@ provided out of the box. ## Implicit Deletion -> TODO: Describe the process of entity providers eagerly deleting entities +Entity providers - not processors - are subject to _eager_ deletion of entities, +which may trigger the implicit deletion of more than just the entity you thought +you were deleting. This concept is explained here. + +Recall that all entity providers manage a private "bucket" of entities, as +described in the [External integrations](external-integrations.md) article. They +can perform some operations on those entities, including additions, updates, and +deletions. Entity additions/updates are subject to the regular processing loops, +which means that bucket entities may end up forming roots of an entire graph of +entities that are emitted by those processors as they recursively work they way +through the bucket contents and its descendants. + +When a provider issues a deletion of an entity in its bucket, that entity as +well as _the entire tree of entities processed out of it_, if any, are +considered for immediate deletion. Note "considered" - they are deleted if and +only if they would otherwise have become orphaned (no other parent entities +emitting them). Since the graph of entities is not strictly a tree, multiple +roots may actually end up indirectly referencing a node farther down in the +graph. If that's the case, that node won't go away until all such roots go away. + +URLs to yaml files that you register using either the Create button or add to +your app-config, are both handled by entity providers. That means that this +implicit deletion mechanism comes into play in some everyday circumstances. +Let's illustrate. + +Imagine that you have a monorepo, with a single `Location` entity in a +catalog-info file at the root, and that entity points to three other +catalog-info files in the repo with a `Component` entity in each one. + +```text +/ + feature_one/ + catalog-info.yaml <- kind: Component + feature_two/ + catalog-info.yaml <- kind: Component + feature_three/ + catalog-info.yaml <- kind: Component + catalog-info.yaml <- kind: Location +``` + +If you register the root `Location` entity, the actual effect is that _five_ +entities appear in the catalog. First, one that is named `generated-`-something, +which corresponds to the registered URL itself. That's the one that the provider +has put in its "bucket". Then, as processing loops chug along, the `Location` +entity you pointed to appears as a child of that, and then the three `Component` +entities appear in turn as children of the `Location`. + +As an end user of the Backstage interface, you may now want to delete one of the +three `Component` entities. You do that by visiting the three-dots menu in the +top right of an entity view. The popup dialog that appears will inform you that +actually this entity belongs to a certain root, and that you may want to remove +that root instead (which corresponds to unregistering the originally registered +URL). If you choose to do so, _all_ of the aforementioned five entities will +actually be deleted in the same operation. + +If you did not want to perform this aggressive pruning, you might have instead +chosen to remove one of the `target` rows of your `Location` catalog-info file, +and then deleted the catalog-info file that contained the `Component` you wanted +to get rid of. Now the catalog would be left with an orphaned component, and you +would instead be able to use the explicit deletion (see below) to delete that +single component. ## Explicit Deletion -> TODO: Describe direct deletion via the catalog API +The catalog and its REST API also permits direct deletion of individual +entities. This makes sense to do on orphaned entities; entities that aren't +being actively kept up to date by any parent entities. The popup interface under +the three-dots menu option of entity views does offer this option, and the +orphaned status can be seen in an info box at the top of the entity's overview +page. + +However, if you were to try to do an explicit depletion on an entity that's +being kept actively updated by a parent entity, it would just reappear again +shortly thereafter when the processing loops reconsider the parent entity that's +still in there. diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index a1dffe1580..415f8cd85e 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -287,6 +287,79 @@ The `RepoUrlPicker` is a custom field that we provide part of the `plugin-scaffolder`. You can provide your own custom fields by [writing your own Custom Field Extensions](./writing-custom-field-extensions.md) +##### Using the Users `oauth` token + +There's a little bit of extra magic that you get out of the box when using the +`RepoUrlPicker` as a field input. You can provide some additional options under +`ui:options` to allow the `RepoUrlPicker` to grab an `oauth` token for the user +for the required `repository`. + +This is great for when you are wanting to create a new repository, or wanting to +perform operations on top of an existing repository. + +A sample template that takes advantage of this is like so: + +```yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: v1beta3-demo + title: Test Action template + description: scaffolder v1beta3 template demo +spec: + owner: backstage/techdocs-core + type: service + + parameters: + ... + + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + # Here's the option you can pass to the RepoUrlPicker + requestUserCredentials: + secretsKey: USER_OAUTH_TOKEN + additionalScopes: + github: + - workflow:write + allowedHosts: + - github.com + ... + + steps: + ... + + - id: publish + name: Publish + action: publish:github + input: + allowedHosts: ['github.com'] + description: This is ${{ parameters.name }} + repoUrl: ${{ parameters.repoUrl }} + # here's where the secret can be used + token: ${{ secrets.USER_OAUTH_TOKEN }} + + ... +``` + +You will see from above that there is an additional `requestUserCredentials` +object that is passed to the `RepoUrlPicker`. This object defines what the +returned `secret` should be stored as when accessing using +`${{ secrets.secretName }}`, in this case it is `USER_OAUTH_TOKEN`. And then you +will see that there is an additional `input` field into the `publish:github` +action called `token`, in which you can use the `secret` like so: +`token: ${{ secrets.USER_OAUTH_TOKEN }}`. + +There's also the ability to pass additional scopes when requesting the `oauth` +token from the user, which you can do on a per-provider basis, in case your +template can be published to multiple providers. + #### The Owner Picker When the scaffolder needs to add new components to the catalog, it needs to have diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index 8a58677af6..d5fd02a46d 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -141,6 +141,63 @@ By convention, such packages should be named `@backstage/analytics-module-[name]`, and any configuration should be keyed under `app.analytics.[name]`. +### Handling User Identity + +If the analytics platform you are integrating with has a first-class concept of +user identity, you can (optionally) choose to support this by the following this +convention: + +- Allow your implementation to be instantiated with the `identityApi` as one of + its options in a `fromConfig` static method. +- Use the `userEntityRef` resolved by `identityApi`'s `getBackstageIdentity()` + method as the basis for the user ID you send to your analytics platform. + +For example: + +```typescript +import { + AnalyticsApi, + analyticsApiRef, + AnyApiFactory, + configApiRef, + createApiFactory, + identityApiRef, + IdentityApi, +} from '@backstage/core-plugin-api'; + +// Implementation that optionally initializes with a userId. +class AcmeAnalytics implements AnalyticsApi { + private constructor(accountId: number, identityApi?: IdentityApi) { + if (identityApi) { + identityApi.getBackstageIdentity().then(identity => { + AcmeAnalytics.init(accountId, { + userId: identity.userEntityRef, + }); + }); + } else { + AcmeAnalytics.init(accountId); + } + } + + static fromConfig(config, options) { + const accountId = config.getString('app.analytics.acme.id'); + return new AcmeAnalytics(accountId, options.identityApi); + } +} + +// Your implementation should be instantiated like this: +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: analyticsApiRef, + deps: { configApi: configApiRef, identityApi: identityApiRef }, + factory: ({ configApi, identityApi }) => + AcmeAnalytics.fromConfig(configApi, { + identityApi, + }), + }), +]; +``` + ## Capturing Events To instrument an event in a component, start by retrieving an analytics tracker diff --git a/package.json b/package.json index 8701ac5f05..5c60cc41c6 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "**/@roadiehq/**/@backstage/plugin-catalog": "*", "**/@roadiehq/**/@backstage/catalog-model": "*" }, - "version": "0.65.0", + "version": "0.66.0", "dependencies": { "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.15.0", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 4bca4d5182..b58d61fcca 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/app-defaults +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/core-app-api@0.5.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/core-app-api@0.5.2-next.0 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.1.5 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index ecd8d11bec..6a509a22f5 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/app-defaults", "description": "Provides the default wiring of a Backstage App", - "version": "0.1.5", + "version": "0.1.6", "private": false, "publishConfig": { "access": "public", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.8.6", - "@backstage/core-app-api": "^0.5.1", + "@backstage/core-components": "^0.8.7", + "@backstage/core-app-api": "^0.5.2", "@backstage/core-plugin-api": "^0.6.0", "@backstage/plugin-permission-react": "^0.3.0", "@backstage/theme": "^0.2.14", @@ -42,8 +42,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@types/jest": "^26.0.7", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 56d38f8e5a..eb9d3cda99 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,117 @@ # example-app +## 0.2.63 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/plugin-code-coverage@0.1.24 + - @backstage/plugin-catalog-common@0.1.2 + - @backstage/cli@0.13.1 + - @backstage/plugin-scaffolder@0.12.1 + - @backstage/integration-react@0.1.20 + - @backstage/plugin-org@0.4.1 + - @backstage/plugin-pagerduty@0.3.24 + - @backstage/plugin-catalog-import@0.8.0 + - @backstage/core-app-api@0.5.2 + - @backstage/plugin-techdocs@0.13.2 + - @backstage/app-defaults@0.1.6 + - @backstage/plugin-airbrake@0.1.2 + - @backstage/plugin-apache-airflow@0.1.5 + - @backstage/plugin-api-docs@0.7.1 + - @backstage/plugin-azure-devops@0.1.13 + - @backstage/plugin-badges@0.2.21 + - @backstage/plugin-catalog@0.7.11 + - @backstage/plugin-catalog-graph@0.2.9 + - @backstage/plugin-circleci@0.2.36 + - @backstage/plugin-cloudbuild@0.2.34 + - @backstage/plugin-cost-insights@0.11.19 + - @backstage/plugin-explore@0.3.28 + - @backstage/plugin-gcp-projects@0.3.16 + - @backstage/plugin-github-actions@0.4.34 + - @backstage/plugin-gocd@0.1.3 + - @backstage/plugin-graphiql@0.2.29 + - @backstage/plugin-home@0.4.13 + - @backstage/plugin-jenkins@0.5.19 + - @backstage/plugin-kafka@0.2.27 + - @backstage/plugin-kubernetes@0.5.6 + - @backstage/plugin-lighthouse@0.2.36 + - @backstage/plugin-newrelic@0.3.15 + - @backstage/plugin-newrelic-dashboard@0.1.5 + - @backstage/plugin-rollbar@0.3.25 + - @backstage/plugin-search@0.6.1 + - @backstage/plugin-sentry@0.3.35 + - @backstage/plugin-shortcuts@0.1.21 + - @backstage/plugin-tech-insights@0.1.7 + - @backstage/plugin-tech-radar@0.5.4 + - @backstage/plugin-todo@0.1.21 + - @backstage/plugin-user-settings@0.3.18 + +## 0.2.63-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/plugin-catalog-react@0.6.13-next.1 + - @backstage/plugin-code-coverage@0.1.24-next.1 + - @backstage/plugin-catalog-common@0.1.2-next.0 + - @backstage/cli@0.13.1-next.1 + - @backstage/plugin-scaffolder@0.12.1-next.1 + - @backstage/core-app-api@0.5.2-next.0 + - @backstage/plugin-techdocs@0.13.2-next.1 + - @backstage/plugin-catalog@0.7.11-next.1 + - @backstage/app-defaults@0.1.6-next.1 + +## 0.2.63-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-org@0.4.1-next.0 + - @backstage/plugin-pagerduty@0.3.24-next.0 + - @backstage/plugin-catalog-import@0.8.0-next.0 + - @backstage/plugin-techdocs@0.13.2-next.0 + - @backstage/plugin-scaffolder@0.12.1-next.0 + - @backstage/cli@0.13.1-next.0 + - @backstage/app-defaults@0.1.6-next.0 + - @backstage/integration-react@0.1.20-next.0 + - @backstage/plugin-airbrake@0.1.2-next.0 + - @backstage/plugin-apache-airflow@0.1.5-next.0 + - @backstage/plugin-api-docs@0.7.1-next.0 + - @backstage/plugin-azure-devops@0.1.13-next.0 + - @backstage/plugin-badges@0.2.21-next.0 + - @backstage/plugin-catalog@0.7.11-next.0 + - @backstage/plugin-catalog-graph@0.2.9-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + - @backstage/plugin-circleci@0.2.36-next.0 + - @backstage/plugin-cloudbuild@0.2.34-next.0 + - @backstage/plugin-code-coverage@0.1.24-next.0 + - @backstage/plugin-cost-insights@0.11.19-next.0 + - @backstage/plugin-explore@0.3.28-next.0 + - @backstage/plugin-gcp-projects@0.3.16-next.0 + - @backstage/plugin-github-actions@0.4.34-next.0 + - @backstage/plugin-gocd@0.1.3-next.0 + - @backstage/plugin-graphiql@0.2.29-next.0 + - @backstage/plugin-home@0.4.13-next.0 + - @backstage/plugin-jenkins@0.5.19-next.0 + - @backstage/plugin-kafka@0.2.27-next.0 + - @backstage/plugin-kubernetes@0.5.6-next.0 + - @backstage/plugin-lighthouse@0.2.36-next.0 + - @backstage/plugin-newrelic@0.3.15-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.5-next.0 + - @backstage/plugin-rollbar@0.3.25-next.0 + - @backstage/plugin-search@0.6.1-next.0 + - @backstage/plugin-sentry@0.3.35-next.0 + - @backstage/plugin-shortcuts@0.1.21-next.0 + - @backstage/plugin-tech-insights@0.1.7-next.0 + - @backstage/plugin-tech-radar@0.5.4-next.0 + - @backstage/plugin-todo@0.1.21-next.0 + - @backstage/plugin-user-settings@0.3.18-next.0 + ## 0.2.62 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 37ae4ad9bd..3447c07059 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,54 +1,56 @@ { "name": "example-app", - "version": "0.2.62", + "version": "0.2.63", "private": true, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^0.1.5", + "@backstage/app-defaults": "^0.1.6", "@backstage/catalog-model": "^0.9.10", - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/core-components": "^0.8.6", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/integration-react": "^0.1.19", - "@backstage/plugin-airbrake": "^0.1.1", - "@backstage/plugin-api-docs": "^0.7.0", - "@backstage/plugin-azure-devops": "^0.1.12", - "@backstage/plugin-apache-airflow": "^0.1.4", - "@backstage/plugin-badges": "^0.2.20", - "@backstage/plugin-catalog": "^0.7.10", - "@backstage/plugin-catalog-graph": "^0.2.8", - "@backstage/plugin-catalog-import": "^0.7.10", - "@backstage/plugin-catalog-react": "^0.6.12", - "@backstage/plugin-circleci": "^0.2.35", - "@backstage/plugin-cloudbuild": "^0.2.33", - "@backstage/plugin-code-coverage": "^0.1.23", - "@backstage/plugin-cost-insights": "^0.11.18", - "@backstage/plugin-explore": "^0.3.27", - "@backstage/plugin-gcp-projects": "^0.3.15", - "@backstage/plugin-github-actions": "^0.4.33", - "@backstage/plugin-gocd": "^0.1.2", - "@backstage/plugin-graphiql": "^0.2.28", - "@backstage/plugin-home": "^0.4.12", - "@backstage/plugin-jenkins": "^0.5.18", - "@backstage/plugin-kafka": "^0.2.26", - "@backstage/plugin-kubernetes": "^0.5.5", - "@backstage/plugin-lighthouse": "^0.2.35", - "@backstage/plugin-newrelic": "^0.3.14", - "@backstage/plugin-newrelic-dashboard": "^0.1.4", - "@backstage/plugin-org": "^0.4.0", - "@backstage/plugin-pagerduty": "0.3.23", - "@backstage/plugin-rollbar": "^0.3.24", - "@backstage/plugin-scaffolder": "^0.12.0", - "@backstage/plugin-search": "^0.6.0", - "@backstage/plugin-sentry": "^0.3.34", - "@backstage/plugin-shortcuts": "^0.1.20", - "@backstage/plugin-tech-radar": "^0.5.3", - "@backstage/plugin-techdocs": "^0.13.1", - "@backstage/plugin-todo": "^0.1.20", - "@backstage/plugin-user-settings": "^0.3.17", + "@backstage/integration-react": "^0.1.20", + "@backstage/plugin-airbrake": "^0.1.2", + "@backstage/plugin-api-docs": "^0.7.1", + "@backstage/plugin-azure-devops": "^0.1.13", + "@backstage/plugin-apache-airflow": "^0.1.5", + "@backstage/plugin-badges": "^0.2.21", + "@backstage/plugin-catalog": "^0.7.11", + "@backstage/plugin-catalog-common": "^0.1.2", + "@backstage/plugin-catalog-graph": "^0.2.9", + "@backstage/plugin-catalog-import": "^0.8.0", + "@backstage/plugin-catalog-react": "^0.6.13", + "@backstage/plugin-circleci": "^0.2.36", + "@backstage/plugin-cloudbuild": "^0.2.34", + "@backstage/plugin-code-coverage": "^0.1.24", + "@backstage/plugin-cost-insights": "^0.11.19", + "@backstage/plugin-explore": "^0.3.28", + "@backstage/plugin-gcp-projects": "^0.3.16", + "@backstage/plugin-github-actions": "^0.4.34", + "@backstage/plugin-gocd": "^0.1.3", + "@backstage/plugin-graphiql": "^0.2.29", + "@backstage/plugin-home": "^0.4.13", + "@backstage/plugin-jenkins": "^0.5.19", + "@backstage/plugin-kafka": "^0.2.27", + "@backstage/plugin-kubernetes": "^0.5.6", + "@backstage/plugin-lighthouse": "^0.2.36", + "@backstage/plugin-newrelic": "^0.3.15", + "@backstage/plugin-newrelic-dashboard": "^0.1.5", + "@backstage/plugin-org": "^0.4.1", + "@backstage/plugin-pagerduty": "0.3.24", + "@backstage/plugin-permission-react": "^0.3.0", + "@backstage/plugin-rollbar": "^0.3.25", + "@backstage/plugin-scaffolder": "^0.12.1", + "@backstage/plugin-search": "^0.6.1", + "@backstage/plugin-sentry": "^0.3.35", + "@backstage/plugin-shortcuts": "^0.1.21", + "@backstage/plugin-tech-radar": "^0.5.4", + "@backstage/plugin-techdocs": "^0.13.2", + "@backstage/plugin-todo": "^0.1.21", + "@backstage/plugin-user-settings": "^0.3.18", "@backstage/search-common": "^0.2.2", - "@backstage/plugin-tech-insights": "^0.1.6", + "@backstage/plugin-tech-insights": "^0.1.7", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -70,7 +72,7 @@ }, "devDependencies": { "@backstage/plugin-permission-react": "^0.3.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/test-utils": "^0.2.4", "@rjsf/core": "^3.2.1", "@testing-library/cypress": "^8.0.2", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 9be424a145..345915189a 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -86,6 +86,8 @@ import * as plugins from './plugins'; import { techDocsPage } from './components/techdocs/TechDocsPage'; import { ApacheAirflowPage } from '@backstage/plugin-apache-airflow'; +import { PermissionedRoute } from '@backstage/plugin-permission-react'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; const app = createApp({ apis, @@ -140,7 +142,11 @@ const routes = ( > {entityPage} - } /> + } + /> /// +import { AbortController as AbortController_2 } from 'node-abort-controller'; import { AbortSignal as AbortSignal_2 } from 'node-abort-controller'; import { AwsS3Integration } from '@backstage/integration'; import { AzureIntegration } from '@backstage/integration'; @@ -13,6 +14,7 @@ import { BitbucketIntegration } from '@backstage/integration'; import { Config } from '@backstage/config'; import cors from 'cors'; import Docker from 'dockerode'; +import { Duration } from 'luxon'; import { ErrorRequestHandler } from 'express'; import express from 'express'; import { GithubCredentialsProvider } from '@backstage/integration'; @@ -145,6 +147,29 @@ export interface ContainerRunner { runContainer(opts: RunContainerOptions): Promise; } +// @alpha +export interface Context { + readonly abortSignal: AbortSignal_2; + readonly deadline: Date | undefined; + value(key: string): T | undefined; +} + +// @alpha +export class Contexts { + static root(): Context; + static withAbort( + parentCtx: Context, + source: AbortController_2 | AbortSignal_2, + ): Context; + static withTimeoutDuration(parentCtx: Context, timeout: Duration): Context; + static withTimeoutMillis(parentCtx: Context, timeout: number): Context; + static withValue( + parentCtx: Context, + key: string, + value: unknown | ((previous: unknown | undefined) => unknown), + ): Context; +} + // @public @deprecated export const createDatabase: typeof createDatabaseClient; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 4040b778bf..fdff7ca414 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,14 +1,15 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.10.5", + "version": "0.10.6", "main": "src/index.ts", "types": "src/index.ts", "private": false, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" }, "homepage": "https://backstage.io", "repository": { @@ -21,7 +22,7 @@ ], "license": "Apache-2.0", "scripts": { - "build": "backstage-cli build --outputs cjs,types", + "build": "backstage-cli build --experimental-type-build --outputs cjs,types", "lint": "backstage-cli lint", "test": "backstage-cli test", "prepack": "backstage-cli prepack", @@ -41,6 +42,7 @@ "@types/cors": "^2.8.6", "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", + "@types/luxon": "^2.0.4", "archiver": "^5.0.2", "aws-sdk": "^2.840.0", "compression": "^1.7.4", @@ -59,6 +61,7 @@ "knex": "^0.95.1", "lodash": "^4.17.21", "logform": "^2.3.2", + "luxon": "^2.0.2", "minimatch": "^3.0.4", "minimist": "^1.2.5", "morgan": "^1.10.0", @@ -81,8 +84,8 @@ } }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/test-utils": "^0.2.4", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", "@types/concat-stream": "^1.6.0", @@ -109,7 +112,8 @@ }, "files": [ "dist", - "config.d.ts" + "config.d.ts", + "alpha" ], "configSchema": "config.d.ts" } diff --git a/packages/backend-common/src/context/AbortContext.test.ts b/packages/backend-common/src/context/AbortContext.test.ts new file mode 100644 index 0000000000..f47e1d4beb --- /dev/null +++ b/packages/backend-common/src/context/AbortContext.test.ts @@ -0,0 +1,345 @@ +/* + * 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 { AbortController } from 'node-abort-controller'; +import { AbortContext } from './AbortContext'; +import { RootContext } from './RootContext'; + +describe('AbortContext', () => { + afterEach(() => { + jest.useRealTimers(); + }); + + describe('forTimeoutMillis', () => { + it('can abort on a timeout', async () => { + jest.useFakeTimers(); + const timeout = 200; + const deadline = Date.now() + timeout; + + const root = new RootContext(); + + const child = AbortContext.forTimeoutMillis(root, timeout); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(child.abortSignal.aborted).toBe(false); + expect(Math.abs(+child.deadline! - deadline)).toBeLessThan(50); + expect(childListener).toBeCalledTimes(0); + + jest.advanceTimersByTime(timeout + 1); + + expect(child.abortSignal.aborted).toBe(true); + expect(childListener).toBeCalledTimes(1); + }); + + it('results in minimum deadline when parent triggers sooner', async () => { + jest.useFakeTimers(); + const parentTimeout = 200; + const childTimeout = 300; + const parentDeadline = Date.now() + parentTimeout; + const childDeadline = parentDeadline; // clamped + + const root = new RootContext(); + + const parent = AbortContext.forTimeoutMillis(root, parentTimeout); + const parentListener = jest.fn(); + parent.abortSignal.addEventListener('abort', parentListener); + + const child = AbortContext.forTimeoutMillis(parent, childTimeout); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(parent.abortSignal.aborted).toBe(false); + expect(child.abortSignal.aborted).toBe(false); + expect(Math.abs(+parent.deadline! - parentDeadline)).toBeLessThan(50); + expect(Math.abs(+child.deadline! - childDeadline)).toBeLessThan(50); + expect(parentListener).toBeCalledTimes(0); + expect(childListener).toBeCalledTimes(0); + + jest.advanceTimersByTime(parentTimeout + 1); + + expect(parent.abortSignal.aborted).toBe(true); + expect(child.abortSignal.aborted).toBe(true); + expect(parentListener).toBeCalledTimes(1); + expect(childListener).toBeCalledTimes(1); + }); + + it('results in minimum deadline when child triggers sooner', async () => { + jest.useFakeTimers(); + const parentTimeout = 300; + const childTimeout = 200; + const parentDeadline = Date.now() + parentTimeout; + const childDeadline = Date.now() + childTimeout; + + const root = new RootContext(); + + const parent = AbortContext.forTimeoutMillis(root, parentTimeout); + const parentListener = jest.fn(); + parent.abortSignal.addEventListener('abort', parentListener); + + const child = AbortContext.forTimeoutMillis(parent, childTimeout); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(parent.abortSignal.aborted).toBe(false); + expect(child.abortSignal.aborted).toBe(false); + expect(Math.abs(+parent.deadline! - parentDeadline)).toBeLessThan(50); + expect(Math.abs(+child.deadline! - childDeadline)).toBeLessThan(50); + expect(parentListener).toBeCalledTimes(0); + expect(childListener).toBeCalledTimes(0); + + jest.advanceTimersByTime(childTimeout + 1); + + expect(parent.abortSignal.aborted).toBe(false); + expect(child.abortSignal.aborted).toBe(true); + expect(parentListener).toBeCalledTimes(0); + expect(childListener).toBeCalledTimes(1); + + jest.advanceTimersByTime(parentTimeout - childTimeout + 1); + + expect(parent.abortSignal.aborted).toBe(true); + expect(child.abortSignal.aborted).toBe(true); + expect(parentListener).toBeCalledTimes(1); + expect(childListener).toBeCalledTimes(1); + }); + + it('child carries over parent signal state if parent was already aborted and had no deadline', async () => { + jest.useFakeTimers(); + const childTimeout = 200; + const childDeadline = Date.now() + childTimeout; + + const root = new RootContext(); + + const parentController = new AbortController(); + const parent = AbortContext.forSignal(root, parentController.signal); + + parentController.abort(); + + const child = AbortContext.forTimeoutMillis(parent, childTimeout); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(child.abortSignal.aborted).toBe(true); + expect(childListener).toBeCalledTimes(0); + expect(Math.abs(+child.deadline! - childDeadline)).toBeLessThan(50); + + jest.advanceTimersByTime(childTimeout + 1); + + expect(child.abortSignal.aborted).toBe(true); + expect(childListener).toBeCalledTimes(0); // still + }); + + it('child carries over parent signal state if parent was already aborted and had a deadline', async () => { + jest.useFakeTimers(); + const first = new RootContext(); + + const secondController = new AbortController(); + const second = AbortContext.forSignal(first, secondController.signal); + secondController.abort(); + + const third = AbortContext.forTimeoutMillis(second, 200); + const fourth = AbortContext.forTimeoutMillis(third, 300); + + expect(third.abortSignal.aborted).toBe(true); + expect(fourth.abortSignal.aborted).toBe(true); + expect(Math.abs(+fourth.deadline! - Date.now() - 200)).toBeLessThan(50); + }); + }); + + describe('forController', () => { + it('signals child when parent is aborted', () => { + const root = new RootContext(); + + const parentController = new AbortController(); + const parent = AbortContext.forController(root, parentController); + const parentListener = jest.fn(); + parent.abortSignal.addEventListener('abort', parentListener); + + const childController = new AbortController(); + const child = AbortContext.forController(parent, childController); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(parent.abortSignal.aborted).toBe(false); + expect(child.abortSignal.aborted).toBe(false); + expect(parentListener).toBeCalledTimes(0); + expect(childListener).toBeCalledTimes(0); + + parentController.abort(); + + expect(parent.abortSignal.aborted).toBe(true); + expect(child.abortSignal.aborted).toBe(true); + expect(parentListener).toBeCalledTimes(1); + expect(childListener).toBeCalledTimes(1); + }); + + it('does not signal parent when child is aborted', async () => { + const root = new RootContext(); + + const parentController = new AbortController(); + const parent = AbortContext.forController(root, parentController); + const parentListener = jest.fn(); + parent.abortSignal.addEventListener('abort', parentListener); + + const childController = new AbortController(); + const child = AbortContext.forController(parent, childController); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(parent.abortSignal.aborted).toBe(false); + expect(child.abortSignal.aborted).toBe(false); + expect(parentListener).toBeCalledTimes(0); + expect(childListener).toBeCalledTimes(0); + + childController.abort(); + + expect(parent.abortSignal.aborted).toBe(false); + expect(child.abortSignal.aborted).toBe(true); + expect(parentListener).toBeCalledTimes(0); + expect(childListener).toBeCalledTimes(1); + }); + + it('child carries over parent signal state if parent was already aborted', async () => { + const root = new RootContext(); + + const parentController = new AbortController(); + const parent = AbortContext.forController(root, parentController); + + parentController.abort(); + + const childController = new AbortController(); + const child = AbortContext.forController(parent, childController); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(parent.abortSignal.aborted).toBe(true); + expect(child.abortSignal.aborted).toBe(true); + expect(childListener).toBeCalledTimes(0); + + childController.abort(); + + expect(parent.abortSignal.aborted).toBe(true); + expect(child.abortSignal.aborted).toBe(true); + expect(childListener).toBeCalledTimes(0); + }); + + it('child carries over given signal state if it was already aborted', async () => { + const root = new RootContext(); + + const childController = new AbortController(); + childController.abort(); + + const child = AbortContext.forController(root, childController); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(child.abortSignal.aborted).toBe(true); + expect(childListener).toBeCalledTimes(0); + }); + }); + + describe('forSignal', () => { + it('signals child when parent is aborted', async () => { + const root = new RootContext(); + + const parentController = new AbortController(); + const parent = AbortContext.forSignal(root, parentController.signal); + const parentListener = jest.fn(); + parent.abortSignal.addEventListener('abort', parentListener); + + const childController = new AbortController(); + const child = AbortContext.forSignal(parent, childController.signal); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(parent.abortSignal.aborted).toBe(false); + expect(child.abortSignal.aborted).toBe(false); + expect(parentListener).toBeCalledTimes(0); + expect(childListener).toBeCalledTimes(0); + + parentController.abort(); + + expect(parent.abortSignal.aborted).toBe(true); + expect(child.abortSignal.aborted).toBe(true); + expect(parentListener).toBeCalledTimes(1); + expect(childListener).toBeCalledTimes(1); + }); + + it('does not signal parent when child is aborted', async () => { + const root = new RootContext(); + + const parentController = new AbortController(); + const parent = AbortContext.forSignal(root, parentController.signal); + const parentListener = jest.fn(); + parent.abortSignal.addEventListener('abort', parentListener); + + const childController = new AbortController(); + const child = AbortContext.forSignal(parent, childController.signal); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(parent.abortSignal.aborted).toBe(false); + expect(child.abortSignal.aborted).toBe(false); + expect(parentListener).toBeCalledTimes(0); + expect(childListener).toBeCalledTimes(0); + + childController.abort(); + + expect(parent.abortSignal.aborted).toBe(false); + expect(child.abortSignal.aborted).toBe(true); + expect(parentListener).toBeCalledTimes(0); + expect(childListener).toBeCalledTimes(1); + }); + + it('child carries over parent signal state if parent was already aborted', async () => { + const root = new RootContext(); + + const parentController = new AbortController(); + const parent = AbortContext.forSignal(root, parentController.signal); + + parentController.abort(); + + const childController = new AbortController(); + const child = AbortContext.forSignal(parent, childController.signal); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(parent.abortSignal.aborted).toBe(true); + expect(child.abortSignal.aborted).toBe(true); + expect(childListener).toBeCalledTimes(0); + + childController.abort(); + + expect(parent.abortSignal.aborted).toBe(true); + expect(child.abortSignal.aborted).toBe(true); + expect(childListener).toBeCalledTimes(0); + }); + + it('child carries over given signal state if it was already aborted', async () => { + const root = new RootContext(); + + const childController = new AbortController(); + childController.abort(); + + const child = AbortContext.forSignal(root, childController.signal); + const childListener = jest.fn(); + child.abortSignal.addEventListener('abort', childListener); + + expect(child.abortSignal.aborted).toBe(true); + expect(childListener).toBeCalledTimes(0); + }); + }); +}); diff --git a/packages/backend-common/src/context/AbortContext.ts b/packages/backend-common/src/context/AbortContext.ts new file mode 100644 index 0000000000..8a119358a6 --- /dev/null +++ b/packages/backend-common/src/context/AbortContext.ts @@ -0,0 +1,133 @@ +/* + * 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 { AbortController, AbortSignal } from 'node-abort-controller'; +import { Context } from './types'; + +/** + * A context that implements various abort related functionality. + */ +export class AbortContext implements Context { + /** + * Abort either when the parent aborts, or after the given timeout has + * expired. + * + * @param ctx - The parent context + * @param timeout - A timeout value, in milliseconds + * @returns A new context + */ + static forTimeoutMillis(ctx: Context, timeout: number): Context { + const desiredDeadline = new Date(Date.now() + timeout); + const actualDeadline = + ctx.deadline && ctx.deadline < desiredDeadline + ? ctx.deadline + : desiredDeadline; + + if (ctx.abortSignal.aborted) { + if (ctx.deadline && desiredDeadline === actualDeadline) { + return ctx; + } + return new AbortContext(ctx, ctx.abortSignal, actualDeadline); + } + + const controller = new AbortController(); + const timeoutHandle = setTimeout(abort, timeout); + ctx.abortSignal.addEventListener('abort', abort); + + function abort() { + ctx.abortSignal.removeEventListener('abort', abort); + clearTimeout(timeoutHandle!); + controller.abort(); + } + + return new AbortContext(ctx, controller.signal, actualDeadline); + } + + /** + * Abort either when the parent aborts, or when the given controller is + * triggered. + * + * @remarks + * + * If you have access to the controller, this function is more efficient than + * {@link AbortContext#forSignal}. + * + * @param ctx - The parent context + * @param controller - An abort controller + * @returns A new context + */ + static forController(ctx: Context, controller: AbortController): Context { + // Already aborted context / signal are fine to reuse as-is + if (ctx.abortSignal.aborted) { + return ctx; + } else if (controller.signal.aborted) { + return new AbortContext(ctx, controller.signal, ctx.deadline); + } + + function abort() { + ctx.abortSignal.removeEventListener('abort', abort); + controller.abort(); + } + + ctx.abortSignal.addEventListener('abort', abort); + + return new AbortContext(ctx, controller.signal, ctx.deadline); + } + + /** + * Abort either when the parent aborts, or when the given signal is triggered. + * + * @remarks + * + * If you have access to the controller and not just the signal, + * {@link AbortContext#forController} is slightly more efficient to use. + * + * @param ctx - The parent context + * @param signal - An abort signal + * @returns A new context + */ + static forSignal(ctx: Context, signal: AbortSignal): Context { + // Already aborted context / signal are fine to reuse as-is + if (ctx.abortSignal.aborted) { + return ctx; + } else if (signal.aborted) { + return new AbortContext(ctx, signal, ctx.deadline); + } + + const controller = new AbortController(); + + function abort() { + ctx.abortSignal.removeEventListener('abort', abort); + signal.removeEventListener('abort', abort); + controller.abort(); + } + + ctx.abortSignal.addEventListener('abort', abort); + signal.addEventListener('abort', abort); + + return new AbortContext(ctx, controller.signal, ctx.deadline); + } + + private constructor( + private readonly parent: Context, + readonly abortSignal: AbortSignal, + readonly deadline: Date | undefined, + ) {} + + value(key: string): T | undefined { + return this.parent.value(key); + } +} diff --git a/packages/backend-common/src/context/Contexts.test.ts b/packages/backend-common/src/context/Contexts.test.ts new file mode 100644 index 0000000000..cbd61d20a9 --- /dev/null +++ b/packages/backend-common/src/context/Contexts.test.ts @@ -0,0 +1,90 @@ +/* + * 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 { Duration } from 'luxon'; +import { AbortController } from 'node-abort-controller'; +import { Contexts } from './Contexts'; + +describe('Contexts', () => { + afterEach(() => { + jest.useRealTimers(); + }); + + describe('root', () => { + it('can create a root', () => { + const ctx = Contexts.root(); + expect(ctx.abortSignal).toBeDefined(); + expect(ctx.deadline).toBeUndefined(); + }); + }); + + describe('setAbort', () => { + it('works for controllers', () => { + const controller = new AbortController(); + const parent = Contexts.root(); + const child = Contexts.withAbort(parent, controller); + expect(child.abortSignal.aborted).toBe(false); + controller.abort(); + expect(child.abortSignal.aborted).toBe(true); + }); + + it('works for signals', () => { + const controller = new AbortController(); + const parent = Contexts.root(); + const child = Contexts.withAbort(parent, controller.signal); + expect(child.abortSignal.aborted).toBe(false); + controller.abort(); + expect(child.abortSignal.aborted).toBe(true); + }); + }); + + describe('setTimeoutDuration', () => { + it('works', () => { + jest.useFakeTimers(); + const parent = Contexts.root(); + const child = Contexts.withTimeoutDuration( + parent, + Duration.fromMillis(200), + ); + expect(child.abortSignal.aborted).toBe(false); + jest.advanceTimersByTime(100); + expect(child.abortSignal.aborted).toBe(false); + jest.advanceTimersByTime(101); + expect(child.abortSignal.aborted).toBe(true); + }); + }); + + describe('setTimeoutMillis', () => { + it('works', () => { + jest.useFakeTimers(); + const parent = Contexts.root(); + const child = Contexts.withTimeoutMillis(parent, 200); + expect(child.abortSignal.aborted).toBe(false); + jest.advanceTimersByTime(100); + expect(child.abortSignal.aborted).toBe(false); + jest.advanceTimersByTime(101); + expect(child.abortSignal.aborted).toBe(true); + }); + }); + + describe('setValue', () => { + it('works', () => { + const parent = Contexts.root(); + const child = Contexts.withValue(parent, 'k', 'v'); + expect(child.value('k')).toBe('v'); + }); + }); +}); diff --git a/packages/backend-common/src/context/Contexts.ts b/packages/backend-common/src/context/Contexts.ts new file mode 100644 index 0000000000..e22a76f673 --- /dev/null +++ b/packages/backend-common/src/context/Contexts.ts @@ -0,0 +1,114 @@ +/* + * 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 { Duration } from 'luxon'; +import { AbortController, AbortSignal } from 'node-abort-controller'; +import { AbortContext } from './AbortContext'; +import { RootContext } from './RootContext'; +import { Context } from './types'; +import { ValueContext } from './ValueContext'; + +/** + * Common context decorators. + * + * @alpha + */ +export class Contexts { + /** + * Creates a root context. + * + * @remarks + * + * This should normally only be called near the root of an application. The + * created context is meant to be passed down into deeper levels, which may or + * may not make derived contexts out of it. + */ + static root(): Context { + return new RootContext(); + } + + /** + * Creates a derived context, which signals to abort operations either when + * any parent context signals, or when the given source is aborted. + * + * @remarks + * + * If the parent context was already aborted, then it is returned as-is. + * + * If the given source was already aborted, then a new already-aborted context + * is returned. + * + * @param parentCtx - A parent context that shall be used as a base + * @param source - An abort controller or signal that you intend to perhaps + * trigger at some later point in time. + * @returns A new {@link Context} + */ + static withAbort( + parentCtx: Context, + source: AbortController | AbortSignal, + ): Context { + return 'aborted' in source + ? AbortContext.forSignal(parentCtx, source) + : AbortContext.forController(parentCtx, source); + } + + /** + * Creates a derived context, which signals to abort operations either when + * any parent context signals, or when the given amount of time has passed. + * This may affect the deadline. + * + * @param parentCtx - A parent context that shall be used as a base + * @param timeout - The duration of time, after which the derived context will + * signal to abort. + * @returns A new {@link Context} + */ + static withTimeoutDuration(parentCtx: Context, timeout: Duration): Context { + return AbortContext.forTimeoutMillis(parentCtx, timeout.as('milliseconds')); + } + + /** + * Creates a derived context, which signals to abort operations either when + * any parent context signals, or when the given amount of time has passed. + * This may affect the deadline. + * + * @param parentCtx - A parent context that shall be used as a base + * @param timeout - The number of milliseconds, after which the derived + * context will signal to abort. + * @returns A new {@link Context} + */ + static withTimeoutMillis(parentCtx: Context, timeout: number): Context { + return AbortContext.forTimeoutMillis(parentCtx, timeout); + } + + /** + * Creates a derived context, which has a specific key-value pair set as well + * as all key-value pairs that were set in the original context. + * + * @param parentCtx - A parent context that shall be used as a base + * @param key - The key of the value to set + * @param value - The value, or a function that accepts the previous value (or + * undefined if not set yet) and computes the new value + * @returns A new {@link Context} + */ + static withValue( + parentCtx: Context, + key: string, + value: unknown | ((previous: unknown | undefined) => unknown), + ): Context { + const v = typeof value === 'function' ? value(parentCtx.value(key)) : value; + return ValueContext.forConstantValue(parentCtx, key, v); + } +} diff --git a/packages/backend-common/src/context/RootContext.test.ts b/packages/backend-common/src/context/RootContext.test.ts new file mode 100644 index 0000000000..395786e3dc --- /dev/null +++ b/packages/backend-common/src/context/RootContext.test.ts @@ -0,0 +1,27 @@ +/* + * 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 { RootContext } from './RootContext'; + +describe('RootContext', () => { + it('returns empty values', async () => { + const ctx = new RootContext(); + expect(ctx.abortSignal).toBeDefined(); + expect(ctx.abortSignal.aborted).toBe(false); + expect(ctx.deadline).toBeUndefined(); + expect(ctx.value('a')).toBeUndefined(); + }); +}); diff --git a/packages/backend-common/src/context/RootContext.ts b/packages/backend-common/src/context/RootContext.ts new file mode 100644 index 0000000000..61962380a4 --- /dev/null +++ b/packages/backend-common/src/context/RootContext.ts @@ -0,0 +1,45 @@ +/* + * 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 { AbortSignal } from 'node-abort-controller'; +import { Context } from './types'; + +/** + * Since the root context can never abort, and since nobody is every meant to + * dispatch events through it, we can use a static dummy instance for + * efficiency. + */ +const dummyAbortSignal: AbortSignal = Object.freeze({ + aborted: false, + addEventListener() {}, + removeEventListener() {}, + dispatchEvent() { + return true; + }, + onabort: null, +}); + +/** + * An empty root context. + */ +export class RootContext implements Context { + readonly abortSignal = dummyAbortSignal; + readonly deadline = undefined; + + value(_key: string): T | undefined { + return undefined; + } +} diff --git a/packages/backend-common/src/context/ValueContext.test.ts b/packages/backend-common/src/context/ValueContext.test.ts new file mode 100644 index 0000000000..6a216d4548 --- /dev/null +++ b/packages/backend-common/src/context/ValueContext.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RootContext } from './RootContext'; +import { ValueContext } from './ValueContext'; + +describe('ValueContext', () => { + it('returns its own values, or delegates to the parent', async () => { + const root = new RootContext(); + const a = ValueContext.forConstantValue(root, 'a', 1); + const b = ValueContext.forConstantValue(a, 'x', 2); + const c = ValueContext.forConstantValue(b, 'a', 3); + const d = ValueContext.forConstantValue(c, 'b', 4); + + expect(a.value('a')).toBe(1); + expect(a.value('b')).toBeUndefined(); + expect(a.value('x')).toBeUndefined(); + + expect(b.value('a')).toBe(1); + expect(b.value('b')).toBeUndefined(); + expect(b.value('x')).toBe(2); + + expect(c.value('a')).toBe(3); + expect(c.value('b')).toBeUndefined(); + expect(c.value('x')).toBe(2); + + expect(d.value('a')).toBe(3); + expect(d.value('b')).toBe(4); + expect(d.value('x')).toBe(2); + }); +}); diff --git a/packages/backend-common/src/context/ValueContext.ts b/packages/backend-common/src/context/ValueContext.ts new file mode 100644 index 0000000000..441aaec984 --- /dev/null +++ b/packages/backend-common/src/context/ValueContext.ts @@ -0,0 +1,46 @@ +/* + * 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 { AbortSignal } from 'node-abort-controller'; +import { Context } from './types'; + +/** + * A context that just holds a single value, and delegates the rest to its + * parent. + */ +export class ValueContext implements Context { + static forConstantValue(ctx: Context, key: string, value: unknown): Context { + return new ValueContext(ctx, key, value); + } + + constructor( + private readonly _parent: Context, + private readonly _key: string, + private readonly _value: unknown, + ) {} + + get abortSignal(): AbortSignal { + return this._parent.abortSignal; + } + + get deadline(): Date | undefined { + return this._parent.deadline; + } + + value(key: string): T | undefined { + return key === this._key ? (this._value as T) : this._parent.value(key); + } +} diff --git a/packages/backend-common/src/context/index.ts b/packages/backend-common/src/context/index.ts new file mode 100644 index 0000000000..37a6e29c8c --- /dev/null +++ b/packages/backend-common/src/context/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export { Contexts } from './Contexts'; +export type { Context } from './types'; diff --git a/packages/backend-common/src/context/types.ts b/packages/backend-common/src/context/types.ts new file mode 100644 index 0000000000..664745bbf6 --- /dev/null +++ b/packages/backend-common/src/context/types.ts @@ -0,0 +1,45 @@ +/* + * 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 { AbortSignal } from 'node-abort-controller'; + +/** + * A context that is meant to be passed as a ctx variable down the call chain, + * to pass along scoped information and abort signals. + * + * @alpha + */ +export interface Context { + /** + * Returns an abort signal that triggers when the current context or any of + * its parents signal for it. + */ + readonly abortSignal: AbortSignal; + + /** + * The point in time when the current context shall time out and abort, if + * applicable. + */ + readonly deadline: Date | undefined; + + /** + * Attempts to get a stored value by key from the context. + * + * @param key - The key of the value to get + * @returns The associated value, or undefined if not set + */ + value(key: string): T | undefined; +} diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index e430ddc1a4..238e58b2a8 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -22,6 +22,7 @@ export * from './cache'; export { loadBackendConfig } from './config'; +export * from './context'; export * from './database'; export * from './discovery'; export * from './hot'; diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index cfaf40a6d6..b13142a7cf 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-tasks +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + ## 0.1.4 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 45a404f457..a060e5e99c 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.1.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", "@backstage/types": "^0.1.1", @@ -43,8 +43,8 @@ "zod": "^3.9.5" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.15", - "@backstage/cli": "^0.13.0", + "@backstage/backend-test-utils": "^0.1.16", + "@backstage/cli": "^0.13.1", "jest": "^26.0.1", "wait-for-expect": "^3.0.2" }, diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 0b3fc1f5b2..0ecc9afb4f 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/backend-test-utils +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.13.1 + - @backstage/backend-common@0.10.6 + +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.13.1-next.1 + - @backstage/backend-common@0.10.6-next.0 + +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.13.1-next.0 + ## 0.1.15 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index dbbc122fc9..b6db1694c3 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-test-utils", "description": "Test helpers library for Backstage backends", - "version": "0.1.15", + "version": "0.1.16", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", - "@backstage/cli": "^0.13.0", + "@backstage/backend-common": "^0.10.6", + "@backstage/cli": "^0.13.1", "@backstage/config": "^0.1.13", "knex": "^0.95.1", "mysql2": "^2.2.5", @@ -41,7 +41,7 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "jest": "^26.0.1" }, "files": [ diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 10962ab2bc..40bfad7ddf 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,85 @@ # example-backend +## 0.2.63 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0 + - @backstage/plugin-rollbar-backend@0.1.20 + - @backstage/plugin-catalog-backend@0.21.2 + - @backstage/plugin-scaffolder-backend@0.15.23 + - @backstage/plugin-proxy-backend@0.2.17 + - @backstage/backend-common@0.10.6 + - example-app@0.2.63 + - @backstage/backend-tasks@0.1.5 + - @backstage/plugin-app-backend@0.3.23 + - @backstage/plugin-azure-devops-backend@0.3.2 + - @backstage/plugin-badges-backend@0.1.17 + - @backstage/plugin-code-coverage-backend@0.1.21 + - @backstage/plugin-graphql-backend@0.1.13 + - @backstage/plugin-jenkins-backend@0.1.12 + - @backstage/plugin-kafka-backend@0.2.16 + - @backstage/plugin-kubernetes-backend@0.4.6 + - @backstage/plugin-permission-backend@0.4.2 + - @backstage/plugin-permission-node@0.4.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.5 + - @backstage/plugin-search-backend@0.4.1 + - @backstage/plugin-search-backend-module-pg@0.2.5 + - @backstage/plugin-tech-insights-backend@0.2.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.7 + - @backstage/plugin-tech-insights-node@0.2.1 + - @backstage/plugin-techdocs-backend@0.13.2 + - @backstage/plugin-todo-backend@0.1.20 + +## 0.2.63-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.1 + - @backstage/backend-common@0.10.6-next.0 + - example-app@0.2.63-next.1 + - @backstage/plugin-catalog-backend@0.21.2-next.1 + - @backstage/plugin-techdocs-backend@0.13.2-next.0 + - @backstage/backend-tasks@0.1.5-next.0 + - @backstage/plugin-app-backend@0.3.23-next.0 + - @backstage/plugin-azure-devops-backend@0.3.2-next.0 + - @backstage/plugin-badges-backend@0.1.17-next.0 + - @backstage/plugin-code-coverage-backend@0.1.21-next.0 + - @backstage/plugin-graphql-backend@0.1.13-next.0 + - @backstage/plugin-jenkins-backend@0.1.12-next.0 + - @backstage/plugin-kafka-backend@0.2.16-next.0 + - @backstage/plugin-kubernetes-backend@0.4.6-next.0 + - @backstage/plugin-permission-backend@0.4.2-next.1 + - @backstage/plugin-permission-node@0.4.2-next.1 + - @backstage/plugin-proxy-backend@0.2.17-next.1 + - @backstage/plugin-rollbar-backend@0.1.20-next.1 + - @backstage/plugin-scaffolder-backend@0.15.23-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.5-next.1 + - @backstage/plugin-search-backend@0.4.1-next.1 + - @backstage/plugin-search-backend-module-pg@0.2.5-next.0 + - @backstage/plugin-tech-insights-backend@0.2.3-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.7-next.0 + - @backstage/plugin-tech-insights-node@0.2.1-next.0 + - @backstage/plugin-todo-backend@0.1.20-next.0 + +## 0.2.63-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.0 + - @backstage/plugin-rollbar-backend@0.1.20-next.0 + - @backstage/plugin-catalog-backend@0.21.2-next.0 + - @backstage/plugin-scaffolder-backend@0.15.23-next.0 + - @backstage/plugin-proxy-backend@0.2.17-next.0 + - @backstage/plugin-permission-backend@0.4.2-next.0 + - @backstage/plugin-permission-node@0.4.2-next.0 + - @backstage/plugin-search-backend@0.4.1-next.0 + - example-app@0.2.63-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.5-next.0 + ## 0.2.62 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 44544703d8..67c959376c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.62", + "version": "0.2.63", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,38 +24,38 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", - "@backstage/backend-tasks": "^0.1.4", + "@backstage/backend-common": "^0.10.6", + "@backstage/backend-tasks": "^0.1.5", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", "@backstage/integration": "^0.7.2", - "@backstage/plugin-app-backend": "^0.3.22", - "@backstage/plugin-auth-backend": "^0.8.0", - "@backstage/plugin-azure-devops-backend": "^0.3.1", - "@backstage/plugin-badges-backend": "^0.1.16", - "@backstage/plugin-catalog-backend": "^0.21.1", - "@backstage/plugin-code-coverage-backend": "^0.1.20", - "@backstage/plugin-graphql-backend": "^0.1.12", - "@backstage/plugin-jenkins-backend": "^0.1.11", - "@backstage/plugin-kubernetes-backend": "^0.4.5", - "@backstage/plugin-kafka-backend": "^0.2.15", - "@backstage/plugin-permission-backend": "^0.4.1", + "@backstage/plugin-app-backend": "^0.3.23", + "@backstage/plugin-auth-backend": "^0.9.0", + "@backstage/plugin-azure-devops-backend": "^0.3.2", + "@backstage/plugin-badges-backend": "^0.1.17", + "@backstage/plugin-catalog-backend": "^0.21.2", + "@backstage/plugin-code-coverage-backend": "^0.1.21", + "@backstage/plugin-graphql-backend": "^0.1.13", + "@backstage/plugin-jenkins-backend": "^0.1.12", + "@backstage/plugin-kubernetes-backend": "^0.4.6", + "@backstage/plugin-kafka-backend": "^0.2.16", + "@backstage/plugin-permission-backend": "^0.4.2", "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/plugin-permission-node": "^0.4.1", - "@backstage/plugin-proxy-backend": "^0.2.16", - "@backstage/plugin-rollbar-backend": "^0.1.19", - "@backstage/plugin-scaffolder-backend": "^0.15.22", - "@backstage/plugin-scaffolder-backend-module-rails": "^0.2.4", - "@backstage/plugin-search-backend": "^0.4.0", + "@backstage/plugin-permission-node": "^0.4.2", + "@backstage/plugin-proxy-backend": "^0.2.17", + "@backstage/plugin-rollbar-backend": "^0.1.20", + "@backstage/plugin-scaffolder-backend": "^0.15.23", + "@backstage/plugin-scaffolder-backend-module-rails": "^0.2.5", + "@backstage/plugin-search-backend": "^0.4.1", "@backstage/plugin-search-backend-node": "^0.4.5", "@backstage/plugin-search-backend-module-elasticsearch": "^0.0.8", - "@backstage/plugin-search-backend-module-pg": "^0.2.4", - "@backstage/plugin-techdocs-backend": "^0.13.1", - "@backstage/plugin-tech-insights-backend": "^0.2.2", - "@backstage/plugin-tech-insights-node": "^0.2.0", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.6", - "@backstage/plugin-todo-backend": "^0.1.19", + "@backstage/plugin-search-backend-module-pg": "^0.2.5", + "@backstage/plugin-techdocs-backend": "^0.13.2", + "@backstage/plugin-tech-insights-backend": "^0.2.3", + "@backstage/plugin-tech-insights-node": "^0.2.1", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.7", + "@backstage/plugin-todo-backend": "^0.1.20", "@gitbeaker/node": "^35.1.0", "@octokit/rest": "^18.5.3", "azure-devops-node-api": "^11.0.1", @@ -72,7 +72,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 543c107e65..a216a70d27 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -35,7 +35,7 @@ "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1-next.0", "@types/jest": "^26.0.7", "msw": "^0.35.0" }, diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 2d9218ec0b..0b9b30fe3e 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -43,7 +43,7 @@ "yup": "^0.32.9" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1-next.0", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", "yaml": "^1.9.2" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 7d63801beb..a74e3ba0a7 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/cli +## 0.13.1 + +### Patch Changes + +- 5bd0ce9e62: chore(deps): bump `inquirer` from 7.3.3 to 8.2.0 +- 80f510caee: Log warning if unable to parse yarn.lock + +## 0.13.1-next.1 + +### Patch Changes + +- 5bd0ce9e62: chore(deps): bump `inquirer` from 7.3.3 to 8.2.0 + +## 0.13.1-next.0 + +### Patch Changes + +- 80f510caee: Log warning if unable to parse yarn.lock + ## 0.13.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 8979387896..aad08a21eb 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.13.0", + "version": "0.13.1", "private": false, "publishConfig": { "access": "public" @@ -78,7 +78,7 @@ "glob": "^7.1.7", "handlebars": "^4.7.3", "html-webpack-plugin": "^5.3.1", - "inquirer": "^7.0.4", + "inquirer": "^8.2.0", "jest": "^26.0.1", "jest-css-modules": "^2.1.0", "json-schema": "^0.4.0", @@ -115,13 +115,13 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@backstage/theme": "^0.2.14", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 8f5b1bcbbc..d14d46efd8 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/codemods +## 0.1.32 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/core-app-api@0.5.2 + +## 0.1.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/core-app-api@0.5.2-next.0 + +## 0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.1.31 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 98ce532aee..4ce5d9700d 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.31", + "version": "0.1.32", "private": false, "publishConfig": { "access": "public", diff --git a/packages/config/package.json b/packages/config/package.json index 44fb7ab286..02c63af2b5 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -34,7 +34,7 @@ "lodash": "^4.17.21" }, "devDependencies": { - "@backstage/test-utils": "^0.2.3", + "@backstage/test-utils": "^0.2.4-next.0", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 06692a0b46..cc115a7da2 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/core-app-api +## 0.5.2 + +### Patch Changes + +- 40775bd263: Switched out the `GithubAuth` implementation to use the common `OAuth2` implementation. This relies on the simultaneous change in `@backstage/plugin-auth-backend` that enabled access token storage in cookies rather than the current solution that's based on `LocalStorage`. + + > **NOTE:** Make sure you upgrade the `auth-backend` deployment before or at the same time as you deploy this change. + +## 0.5.2-next.0 + +### Patch Changes + +- 40775bd263: Switched out the `GithubAuth` implementation to use the common `OAuth2` implementation. This relies on the simultaneous change in `@backstage/plugin-auth-backend` that enabled access token storage in cookies rather than the current solution that's based on `LocalStorage`. + + > **NOTE:** Make sure you upgrade the `auth-backend` deployment before or at the same time as you deploy this change. + ## 0.5.1 ### Patch Changes diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index ba2ed8cb62..3b53813615 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -35,6 +35,7 @@ import { FeatureFlag } from '@backstage/core-plugin-api'; import { FeatureFlagsApi } from '@backstage/core-plugin-api'; import { FeatureFlagsSaveOptions } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; +import { githubAuthApiRef } from '@backstage/core-plugin-api'; import { gitlabAuthApiRef } from '@backstage/core-plugin-api'; import { googleAuthApiRef } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; @@ -379,25 +380,11 @@ export type FlatRoutesProps = { }; // @public -export class GithubAuth implements OAuthApi, SessionApi { - // (undocumented) - static create(options: OAuthApiCreateOptions): GithubAuth; - // (undocumented) - getAccessToken(scope?: string, options?: AuthRequestOptions): Promise; - // (undocumented) - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; - // (undocumented) - getProfile(options?: AuthRequestOptions): Promise; +export class GithubAuth { // (undocumented) + static create(options: OAuthApiCreateOptions): typeof githubAuthApiRef.T; + // @deprecated (undocumented) static normalizeScope(scope?: string): Set; - // (undocumented) - sessionState$(): Observable; - // (undocumented) - signIn(): Promise; - // (undocumented) - signOut(): Promise; } // @public @deprecated diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index e6bf5e9582..a546e29134 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.5.1", + "version": "0.5.2", "private": false, "publishConfig": { "access": "public", @@ -45,8 +45,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts index 8bcd4cb7a5..ea7b4ac688 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts @@ -14,16 +14,33 @@ * limitations under the License. */ +import { UrlPatternDiscovery } from '../../DiscoveryApi'; +import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; import GithubAuth from './GithubAuth'; -describe('GithubAuth', () => { - it('should get access token', async () => { - const getSession = jest - .fn() - .mockResolvedValue({ providerInfo: { accessToken: 'access-token' } }); - const githubAuth = new (GithubAuth as any)({ getSession }) as GithubAuth; +const getSession = jest.fn(); - expect(await githubAuth.getAccessToken()).toBe('access-token'); - expect(getSession).toBeCalledTimes(1); +jest.mock('../../../../lib/AuthSessionManager', () => ({ + ...(jest.requireActual('../../../../lib/AuthSessionManager') as any), + RefreshingAuthSessionManager: class { + getSession = getSession; + }, +})); + +describe('GithubAuth', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should forward access token request to session manager', async () => { + const githubAuth = GithubAuth.create({ + oauthRequestApi: new MockOAuthApi(), + discoveryApi: UrlPatternDiscovery.compile('http://example.com'), + }); + + githubAuth.getAccessToken('repo'); + expect(getSession).toHaveBeenCalledWith({ + scopes: new Set(['repo']), + }); }); }); diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts index dc2c15bc16..b0af4c7ade 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts @@ -14,35 +14,9 @@ * limitations under the License. */ -import { - AuthRequestOptions, - BackstageIdentityResponse, - OAuthApi, - ProfileInfo, - SessionApi, - SessionState, -} from '@backstage/core-plugin-api'; -import { Observable } from '@backstage/types'; -import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; -import { - AuthSessionStore, - RefreshingAuthSessionManager, - StaticAuthSessionManager, -} from '../../../../lib/AuthSessionManager'; -import { OptionalRefreshSessionManagerMux } from '../../../../lib/AuthSessionManager/OptionalRefreshSessionManagerMux'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; +import { githubAuthApiRef } from '@backstage/core-plugin-api'; +import { OAuth2 } from '../oauth2'; import { OAuthApiCreateOptions } from '../types'; -import { GithubSession, githubSessionSchema } from './types'; - -export type GithubAuthResponse = { - providerInfo: { - accessToken: string; - scope: string; - expiresInSeconds?: number; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentityResponse; -}; const DEFAULT_PROVIDER = { id: 'github', @@ -55,8 +29,8 @@ const DEFAULT_PROVIDER = { * * @public */ -export default class GithubAuth implements OAuthApi, SessionApi { - static create(options: OAuthApiCreateOptions) { +export default class GithubAuth { + static create(options: OAuthApiCreateOptions): typeof githubAuthApiRef.T { const { discoveryApi, environment = 'development', @@ -65,96 +39,18 @@ export default class GithubAuth implements OAuthApi, SessionApi { defaultScopes = ['read:user'], } = options; - const connector = new DefaultAuthConnector({ + return OAuth2.create({ discoveryApi, - environment, + oauthRequestApi, provider, - oauthRequestApi: oauthRequestApi, - sessionTransform(res: GithubAuthResponse): GithubSession { - return { - ...res, - providerInfo: { - accessToken: res.providerInfo.accessToken, - scopes: GithubAuth.normalizeScope(res.providerInfo.scope), - expiresAt: res.providerInfo.expiresInSeconds - ? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1000) - : undefined, - }, - }; - }, + environment, + defaultScopes, }); - - const refreshingSessionManager = new RefreshingAuthSessionManager({ - connector, - defaultScopes: new Set(defaultScopes), - sessionScopes: (session: GithubSession) => session.providerInfo.scopes, - sessionShouldRefresh: (session: GithubSession) => { - const { expiresAt } = session.providerInfo; - if (!expiresAt) { - return false; - } - const expiresInSec = (expiresAt.getTime() - Date.now()) / 1000; - return expiresInSec < 60 * 5; - }, - }); - - const staticSessionManager = new AuthSessionStore({ - manager: new StaticAuthSessionManager({ - connector, - defaultScopes: new Set(defaultScopes), - sessionScopes: (session: GithubSession) => session.providerInfo.scopes, - }), - storageKey: `${provider.id}Session`, - schema: githubSessionSchema, - sessionScopes: (session: GithubSession) => session.providerInfo.scopes, - }); - - const sessionManagerMux = new OptionalRefreshSessionManagerMux({ - refreshingSessionManager, - staticSessionManager, - sessionCanRefresh: session => - session.providerInfo.expiresAt !== undefined, - }); - - return new GithubAuth(sessionManagerMux); - } - - private constructor( - private readonly sessionManager: SessionManager, - ) {} - - async signIn() { - await this.getAccessToken(); - } - - async signOut() { - await this.sessionManager.removeSession(); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - async getAccessToken(scope?: string, options?: AuthRequestOptions) { - const session = await this.sessionManager.getSession({ - ...options, - scopes: GithubAuth.normalizeScope(scope), - }); - return session?.providerInfo.accessToken ?? ''; - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; } + /** + * @deprecated This method is deprecated and will be removed in a future release. + */ static normalizeScope(scope?: string): Set { if (!scope) { return new Set(); diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index abbcab5e49..27cf543205 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/core-components +## 0.8.7 + +### Patch Changes + +- f7257dff6f: The `` component now accepts a `noTrack` prop, which prevents the `click` event from being captured by the Analytics API. This can be used if tracking is explicitly not warranted, or in order to use custom link tracking in specific situations. +- 4c773ed25c: Change subtitle of Header style to use palette.bursts.fontColor +- f465b63b7f: Fix an issue where changes related to the `MobileSidebar` prevented scrolling pages. Additionally improve the menu of the `MobileSidebar` to not overlay the `BottomNavigation`. +- 064e750a50: Adding hover message to the Gauge and an info icon to the GaugeCard. +- a681cb9c2f: Make linkTarget configurable for MarkdownContent component + +## 0.8.7-next.1 + +### Patch Changes + +- f7257dff6f: The `` component now accepts a `noTrack` prop, which prevents the `click` event from being captured by the Analytics API. This can be used if tracking is explicitly not warranted, or in order to use custom link tracking in specific situations. + +## 0.8.7-next.0 + +### Patch Changes + +- 4c773ed25c: Change subtitle of Header style to use palette.bursts.fontColor +- f465b63b7f: Fix an issue where changes related to the `MobileSidebar` prevented scrolling pages. Additionally improve the menu of the `MobileSidebar` to not overlay the `BottomNavigation`. +- a681cb9c2f: Make linkTarget configurable for MarkdownContent component + ## 0.8.6 ### Patch Changes diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 88e7f4efab..6365b4f9d7 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -384,7 +384,12 @@ export function GaugeCard(props: Props_10): JSX.Element; export type GaugeCardClassKey = 'root'; // @public (undocumented) -export type GaugeClassKey = 'root' | 'overlay' | 'circle' | 'colorUnknown'; +export type GaugeClassKey = + | 'root' + | 'overlay' + | 'description' + | 'circle' + | 'colorUnknown'; // @public (undocumented) export type GaugeProps = { @@ -393,6 +398,7 @@ export type GaugeProps = { inverse?: boolean; unit?: string; max?: number; + description?: ReactNode; getColor?: GaugePropsGetColor; }; @@ -599,6 +605,7 @@ export function Link(props: LinkProps): JSX.Element; export type LinkProps = LinkProps_2 & LinkProps_3 & { component?: ElementType; + noTrack?: boolean; }; // Warning: (ae-missing-release-tag) "LoginRequestListItemClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 8037d52837..54ab87f63d 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.8.6", + "version": "0.8.7", "private": false, "publishConfig": { "access": "public", @@ -73,9 +73,9 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/core-app-api": "^0.5.1", - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/core-app-api": "^0.5.2", + "@backstage/cli": "^0.13.1", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx index 2d00aad827..8c6ed54d76 100644 --- a/packages/core-components/src/components/Link/Link.test.tsx +++ b/packages/core-components/src/components/Link/Link.test.tsx @@ -76,6 +76,33 @@ describe('', () => { }); }); + it('does not capture click when noTrack is set', async () => { + const linkText = 'Navigate!'; + const analyticsApi = new MockAnalyticsApi(); + const customOnClick = jest.fn(); + + const { getByText } = render( + wrapInTestApp( + + + {linkText} + + , + ), + ); + + fireEvent.click(getByText(linkText)); + + // Analytics event should have been fired. + await waitFor(() => { + // Custom onClick handler should have been fired. + expect(customOnClick).toHaveBeenCalled(); + + // But there should be no analytics event. + expect(analyticsApi.getEvents()).toHaveLength(0); + }); + }); + describe('isExternalUri', () => { it.each([ [true, 'http://'], diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index 8866150f86..30796c4e05 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -29,6 +29,7 @@ export const isExternalUri = (uri: string) => /^([a-z+.-]+):/.test(uri); export type LinkProps = MaterialLinkProps & RouterLinkProps & { component?: ElementType; + noTrack?: boolean; }; declare function LinkType(props: LinkProps): JSX.Element; @@ -62,7 +63,7 @@ const getNodeText = (node: React.ReactNode): string => { * - Captures Link clicks as analytics events. */ const ActualLink = React.forwardRef( - ({ onClick, ...props }, ref) => { + ({ onClick, noTrack, ...props }, ref) => { const analytics = useAnalytics(); const to = String(props.to); const linkText = getNodeText(props.children) || to; @@ -71,7 +72,9 @@ const ActualLink = React.forwardRef( const handleClick = (event: React.MouseEvent) => { onClick?.(event); - analytics.captureEvent('click', linkText, { attributes: { to } }); + if (!noTrack) { + analytics.captureEvent('click', linkText, { attributes: { to } }); + } }; return external ? ( diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx index 0336ebc990..11f977b387 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx @@ -17,10 +17,15 @@ import { BackstagePalette, BackstageTheme } from '@backstage/theme'; import { makeStyles, useTheme } from '@material-ui/core/styles'; import { Circle } from 'rc-progress'; -import React from 'react'; +import React, { ReactNode, useEffect, useState } from 'react'; /** @public */ -export type GaugeClassKey = 'root' | 'overlay' | 'circle' | 'colorUnknown'; +export type GaugeClassKey = + | 'root' + | 'overlay' + | 'description' + | 'circle' + | 'colorUnknown'; const useStyles = makeStyles( theme => ({ @@ -37,6 +42,15 @@ const useStyles = makeStyles( fontWeight: 'bold', color: theme.palette.textContrast, }, + description: { + fontSize: '100%', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + position: 'absolute', + wordBreak: 'break-all', + display: 'inline-block', + }, circle: { width: '80%', transform: 'translate(10%, 0)', @@ -53,6 +67,7 @@ export type GaugeProps = { inverse?: boolean; unit?: string; max?: number; + description?: ReactNode; getColor?: GaugePropsGetColor; }; @@ -104,10 +119,11 @@ export const getProgressColor: GaugePropsGetColor = ({ */ export function Gauge(props: GaugeProps) { + const [hoverRef, setHoverRef] = useState(null); const { getColor = getProgressColor } = props; const classes = useStyles(props); const { palette } = useTheme(); - const { value, fractional, inverse, unit, max } = { + const { value, fractional, inverse, unit, max, description } = { ...defaultGaugeProps, ...props, }; @@ -115,8 +131,28 @@ export function Gauge(props: GaugeProps) { const asPercentage = fractional ? Math.round(value * max) : value; const asActual = max !== 100 ? Math.round(value) : asPercentage; + const [isHovering, setIsHovering] = useState(false); + + useEffect(() => { + const node = hoverRef; + const handleMouseOver = () => setIsHovering(true); + const handleMouseOut = () => setIsHovering(false); + if (node && description) { + node.addEventListener('mouseenter', handleMouseOver); + node.addEventListener('mouseleave', handleMouseOut); + + return () => { + node.removeEventListener('mouseenter', handleMouseOver); + node.removeEventListener('mouseleave', handleMouseOut); + }; + } + return () => { + setIsHovering(false); + }; + }, [description, hoverRef]); + return ( -
+
-
- {isNaN(value) ? 'N/A' : `${asActual}${unit}`} -
+ {description && isHovering ? ( +
{description}
+ ) : ( +
+ {isNaN(value) ? 'N/A' : `${asActual}${unit}`} +
+ )}
); } diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index 64a6cc1b16..08c7813643 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -18,6 +18,8 @@ import React, { PropsWithChildren } from 'react'; import { GaugeCard } from './GaugeCard'; import Grid from '@material-ui/core/Grid'; import { MemoryRouter } from 'react-router'; +import Tooltip from '@material-ui/core/Tooltip'; +import Info from '@material-ui/icons/Info'; const linkInfo = { title: 'Go to XYZ Location', link: '#' }; @@ -118,3 +120,79 @@ export const StaticColor = () => ( ); + +export const InfoMessage = () => ( + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + +); + +export const HoverMessage = () => ( + + + + + + + + + + + + + + +); diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx index 9e6c19ebfe..1114890523 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx @@ -15,7 +15,7 @@ */ import { makeStyles } from '@material-ui/core/styles'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { BottomLinkProps } from '../../layout/BottomLink'; import { InfoCard, InfoCardVariants } from '../../layout/InfoCard'; import { Gauge, GaugePropsGetColor } from './Gauge'; @@ -26,6 +26,8 @@ type Props = { variant?: InfoCardVariants; /** Progress in % specified as decimal, e.g. "0.23" */ progress: number; + description?: ReactNode; + icon?: ReactNode; inverse?: boolean; deepLink?: BottomLinkProps; getColor?: GaugePropsGetColor; @@ -52,11 +54,21 @@ const useStyles = makeStyles( */ export function GaugeCard(props: Props) { const classes = useStyles(props); - const { title, subheader, progress, inverse, deepLink, variant, getColor } = - props; + const { + title, + subheader, + progress, + inverse, + deepLink, + description, + icon, + variant, + getColor, + } = props; const gaugeProps = { inverse, + description, getColor, value: progress, }; @@ -68,6 +80,7 @@ export function GaugeCard(props: Props) { subheader={subheader} deepLink={deepLink} variant={variant} + icon={icon} > diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index f1e67904f1..256d60cfa2 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -55,6 +55,9 @@ const useStyles = makeStyles( headerAvatar: {}, headerAction: {}, headerContent: {}, + subheader: { + display: 'flex', + }, }), { name: 'BackstageInfoCard' }, ); @@ -134,6 +137,7 @@ type Props = { children?: ReactNode; headerStyle?: object; headerProps?: CardHeaderProps; + icon?: ReactNode; action?: ReactNode; actionsClassName?: string; actions?: ReactNode; @@ -162,6 +166,7 @@ export function InfoCard(props: Props): JSX.Element { children, headerStyle, headerProps, + icon, action, actionsClassName, actions, @@ -194,6 +199,15 @@ export function InfoCard(props: Props): JSX.Element { }); } + const cardSubTitle = () => { + return ( +
+ {subheader &&
{subheader}
} + {icon} +
+ ); + }; + const errProps: ErrorBoundaryProps = errorBoundaryProps || (slackChannel ? { slackChannel } : {}); @@ -211,7 +225,7 @@ export function InfoCard(props: Props): JSX.Element { content: classes.headerContent, }} title={title} - subheader={subheader} + subheader={cardSubTitle()} action={action} style={{ ...headerStyle }} titleTypographyProps={titleTypographyProps} diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 10146ee8e4..5186fca4dd 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -43,9 +43,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1-next.1", + "@backstage/core-app-api": "^0.5.2-next.0", + "@backstage/test-utils": "^0.2.4-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 6200003dc4..6cb892905b 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,161 @@ # @backstage/create-app +## 0.4.18 + +### Patch Changes + +- 5bd0ce9e62: chore(deps): bump `inquirer` from 7.3.3 to 8.2.0 +- f27f5197e2: Apply the fix from `0.4.16`, which is part of the `v0.65.1` release of Backstage. +- 2687029a67: Update backend-to-backend auth link in configuration file comment +- 24ef62048c: Adds missing `/catalog-graph` route to ``. + + To fix this problem for a recently created app please update your `app/src/App.tsx` + + ```diff + + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; + + ... omitted ... + + + } /> + + } /> + + ``` + +- ba59832aed: Permission the `catalog-import` route + + The following changes are **required** if you intend to add permissions to your existing app. + + Use the `PermissionedRoute` for `CatalogImportPage` instead of the normal `Route`: + + ```diff + // packages/app/src/App.tsx + ... + + import { PermissionedRoute } from '@backstage/plugin-permission-react'; + + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; + + ... + + - } /> + + } + + /> + ``` + +- cef64b1561: Added `tokenManager` as a required property for the auth-backend `createRouter` function. This dependency is used to issue server tokens that are used by the `CatalogIdentityClient` when looking up users and their group membership during authentication. + + These changes are **required** to `packages/backend/src/plugins/auth.ts`: + + ```diff + export default async function createPlugin({ + logger, + database, + config, + discovery, + + tokenManager, + }: PluginEnvironment): Promise { + return await createRouter({ + logger, + config, + database, + discovery, + + tokenManager, + }); + } + ``` + +- e39d88bd84: Switched the `app` dependency in the backend to use a file target rather than version. + + To apply this change to an existing app, make the following change to `packages/backend/package.json`: + + ```diff + "dependencies": { + - "app": "0.0.0", + + "app": "file:../app", + ``` + +## 0.4.18-next.1 + +### Patch Changes + +- 5bd0ce9e62: chore(deps): bump `inquirer` from 7.3.3 to 8.2.0 +- ba59832aed: Permission the `catalog-import` route + + The following changes are **required** if you intend to add permissions to your existing app. + + Use the `PermissionedRoute` for `CatalogImportPage` instead of the normal `Route`: + + ```diff + // packages/app/src/App.tsx + ... + + import { PermissionedRoute } from '@backstage/plugin-permission-react'; + + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; + + ... + + - } /> + + } + + /> + ``` + +## 0.4.18-next.0 + +### Patch Changes + +- f27f5197e2: Apply the fix from `0.4.16`, which is part of the `v0.65.1` release of Backstage. +- 2687029a67: Update backend-to-backend auth link in configuration file comment +- 24ef62048c: Adds missing `/catalog-graph` route to ``. + + To fix this problem for a recently created app please update your `app/src/App.tsx` + + ```diff + + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; + + ... omitted ... + + + } /> + + } /> + + ``` + +- cef64b1561: Added `tokenManager` as a required property for the auth-backend `createRouter` function. This dependency is used to issue server tokens that are used by the `CatalogIdentityClient` when looking up users and their group membership during authentication. + + These changes are **required** to `packages/backend/src/plugins/auth.ts`: + + ```diff + export default async function createPlugin({ + logger, + database, + config, + discovery, + + tokenManager, + }: PluginEnvironment): Promise { + return await createRouter({ + logger, + config, + database, + discovery, + + tokenManager, + }); + } + ``` + +- e39d88bd84: Switched the `app` dependency in the backend to use a file target rather than version. + + To apply this change to an existing app, make the following change to `packages/backend/package.json`: + + ```diff + "dependencies": { + - "app": "0.0.0", + + "app": "file:../app", + ``` + ## 0.4.16 ### Patch Changes @@ -49,7 +205,7 @@ To make this change to an existing app: - Add `@backstage/catalog-graph-plugin` as a `dependency` in `packages/app/package.json` + Add `@backstage/plugin-catalog-graph` as a `dependency` in `packages/app/package.json` Apply the following changes to the `packages/app/src/components/catalog/EntityPage.tsx` file: diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 3d7dfead49..a679942a53 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.4.15", + "version": "0.4.18", "private": false, "publishConfig": { "access": "public" @@ -35,7 +35,7 @@ "commander": "^6.1.0", "fs-extra": "9.1.0", "handlebars": "^4.7.3", - "inquirer": "^7.0.4", + "inquirer": "^8.2.0", "ora": "^5.3.0", "recursive-readdir": "^2.2.2" }, diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 34d016ebde..7179b7924f 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -49,6 +49,7 @@ import { version as pluginApiDocs } from '../../../../plugins/api-docs/package.j import { version as pluginAppBackend } from '../../../../plugins/app-backend/package.json'; import { version as pluginAuthBackend } from '../../../../plugins/auth-backend/package.json'; import { version as pluginCatalog } from '../../../../plugins/catalog/package.json'; +import { version as pluginCatalogCommon } from '../../../../plugins/catalog-common/package.json'; import { version as pluginCatalogReact } from '../../../../plugins/catalog-react/package.json'; import { version as pluginCatalogBackend } from '../../../../plugins/catalog-backend/package.json'; import { version as pluginCatalogGraph } from '../../../../plugins/catalog-graph/package.json'; @@ -59,6 +60,7 @@ import { version as pluginGithubActions } from '../../../../plugins/github-actio import { version as pluginLighthouse } from '../../../../plugins/lighthouse/package.json'; import { version as pluginOrg } from '../../../../plugins/org/package.json'; import { version as pluginPermissionCommon } from '../../../../plugins/permission-common/package.json'; +import { version as pluginPermissionReact } from '../../../../plugins/permission-react/package.json'; import { version as pluginPermissionNode } from '../../../../plugins/permission-node/package.json'; import { version as pluginProxyBackend } from '../../../../plugins/proxy-backend/package.json'; import { version as pluginRollbarBackend } from '../../../../plugins/rollbar-backend/package.json'; @@ -89,6 +91,7 @@ export const packageVersions = { '@backstage/plugin-app-backend': pluginAppBackend, '@backstage/plugin-auth-backend': pluginAuthBackend, '@backstage/plugin-catalog': pluginCatalog, + '@backstage/plugin-catalog-common': pluginCatalogCommon, '@backstage/plugin-catalog-react': pluginCatalogReact, '@backstage/plugin-catalog-backend': pluginCatalogBackend, '@backstage/plugin-catalog-graph': pluginCatalogGraph, @@ -100,6 +103,7 @@ export const packageVersions = { '@backstage/plugin-org': pluginOrg, '@backstage/plugin-permission-common': pluginPermissionCommon, '@backstage/plugin-permission-node': pluginPermissionNode, + '@backstage/plugin-permission-react': pluginPermissionReact, '@backstage/plugin-proxy-backend': pluginProxyBackend, '@backstage/plugin-rollbar-backend': pluginRollbarBackend, '@backstage/plugin-scaffolder': pluginScaffolder, 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 23c45d3f9d..53e0590db4 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 @@ -13,11 +13,13 @@ "@backstage/integration-react": "^{{version '@backstage/integration-react'}}", "@backstage/plugin-api-docs": "^{{version '@backstage/plugin-api-docs'}}", "@backstage/plugin-catalog": "^{{version '@backstage/plugin-catalog'}}", + "@backstage/plugin-catalog-common": "^{{version '@backstage/plugin-catalog-common'}}", "@backstage/plugin-catalog-graph": "^{{version '@backstage/plugin-catalog-graph'}}", "@backstage/plugin-catalog-import": "^{{version '@backstage/plugin-catalog-import'}}", "@backstage/plugin-catalog-react": "^{{version '@backstage/plugin-catalog-react'}}", "@backstage/plugin-github-actions": "^{{version '@backstage/plugin-github-actions'}}", "@backstage/plugin-org": "^{{version '@backstage/plugin-org'}}", + "@backstage/plugin-permission-react": "^{{version '@backstage/plugin-permission-react'}}", "@backstage/plugin-scaffolder": "^{{version '@backstage/plugin-scaffolder'}}", "@backstage/plugin-search": "^{{version '@backstage/plugin-search'}}", "@backstage/plugin-tech-radar": "^{{version '@backstage/plugin-tech-radar'}}", diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 5a379b5c8f..9b6518620d 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -30,6 +30,8 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; import { createApp } from '@backstage/app-defaults'; import { FlatRoutes } from '@backstage/core-app-api'; import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +import { PermissionedRoute } from '@backstage/plugin-permission-react'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; const app = createApp({ apis, @@ -76,7 +78,11 @@ const routes = ( path="/tech-radar" element={} /> - } /> + } + /> }> {searchPage} diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index a170884538..2e34bcaefa 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/dev-utils +## 0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/integration-react@0.1.20 + - @backstage/core-app-api@0.5.2 + - @backstage/app-defaults@0.1.6 + - @backstage/test-utils@0.2.4 + +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/plugin-catalog-react@0.6.13-next.1 + - @backstage/core-app-api@0.5.2-next.0 + - @backstage/app-defaults@0.1.6-next.1 + - @backstage/test-utils@0.2.4-next.0 + +## 0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/app-defaults@0.1.6-next.0 + - @backstage/integration-react@0.1.20-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.2.19 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index bf29c742a9..1f59a143e1 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.2.19", + "version": "0.2.20", "private": false, "publishConfig": { "access": "public", @@ -29,14 +29,14 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/app-defaults": "^0.1.5", - "@backstage/core-app-api": "^0.5.1", - "@backstage/core-components": "^0.8.6", + "@backstage/app-defaults": "^0.1.6", + "@backstage/core-app-api": "^0.5.2", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/catalog-model": "^0.9.10", - "@backstage/integration-react": "^0.1.19", - "@backstage/plugin-catalog-react": "^0.6.12", - "@backstage/test-utils": "^0.2.3", + "@backstage/integration-react": "^0.1.20", + "@backstage/plugin-catalog-react": "^0.6.13", + "@backstage/test-utils": "^0.2.4", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -55,7 +55,7 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/errors/package.json b/packages/errors/package.json index 53a0ea93b8..7861793aef 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -35,7 +35,7 @@ "serialize-error": "^8.0.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1-next.0", "@types/jest": "^26.0.7" }, "files": [ diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index c7bb73285f..4e191866b4 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/integration-react +## 0.1.20 + +### Patch Changes + +- cee44ad289: Added the ability to collect users `oauth` token from the `RepoUrlPicker` for use in the template manifest +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.1.19 ### Patch Changes diff --git a/packages/integration-react/api-report.md b/packages/integration-react/api-report.md index 2db29631a3..5b0c9d6174 100644 --- a/packages/integration-react/api-report.md +++ b/packages/integration-react/api-report.md @@ -29,6 +29,7 @@ export class ScmAuth implements ScmAuthApi { ProfileInfoApi & BackstageIdentityApi & SessionApi; + bitbucket: OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi; } >; static forAuthApi( @@ -82,6 +83,12 @@ export const scmAuthApiRef: ApiRef; export interface ScmAuthTokenOptions extends AuthRequestOptions { additionalScope?: { repoWrite?: boolean; + customScopes?: { + github?: string[]; + azure?: string[]; + bitbucket?: string[]; + gitlab?: string[]; + }; }; url: string; } diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index c8aa4fd3b9..255565fafa 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration-react", "description": "Frontend package for managing integrations towards external systems", - "version": "0.1.19", + "version": "0.1.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/integration": "^0.7.2", "@backstage/theme": "^0.2.14", @@ -35,9 +35,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/integration-react/src/api/ScmAuth.test.ts b/packages/integration-react/src/api/ScmAuth.test.ts index 2d0ea9b97d..4ebfef749e 100644 --- a/packages/integration-react/src/api/ScmAuth.test.ts +++ b/packages/integration-react/src/api/ScmAuth.test.ts @@ -141,6 +141,58 @@ describe('ScmAuth', () => { }); }); + it('should support additional provided scopes from the caller', async () => { + const mockAuthApi = { + getAccessToken: async (scopes: string[]) => { + return scopes.join(' '); + }, + }; + + const githubAuth = ScmAuth.forGithub(mockAuthApi); + await expect( + githubAuth.getCredentials({ + url: 'http://example.com', + additionalScope: { + customScopes: { github: ['org:read', 'workflow:write'] }, + }, + }), + ).resolves.toMatchObject({ + token: 'repo read:org read:user org:read workflow:write', + }); + + const gitlabAuth = ScmAuth.forGitlab(mockAuthApi); + await expect( + gitlabAuth.getCredentials({ + url: 'http://example.com', + additionalScope: { customScopes: { gitlab: ['write_repository'] } }, + }), + ).resolves.toMatchObject({ + token: 'read_user read_api read_repository write_repository', + }); + + const azureAuth = ScmAuth.forAzure(mockAuthApi); + await expect( + azureAuth.getCredentials({ + url: 'http://example.com', + additionalScope: { customScopes: { azure: ['vso.org'] } }, + }), + ).resolves.toMatchObject({ + token: 'vso.build vso.code vso.graph vso.project vso.profile vso.org', + }); + + const bitbucketAuth = ScmAuth.forBitbucket(mockAuthApi); + await expect( + bitbucketAuth.getCredentials({ + url: 'http://example.com', + additionalScope: { + customScopes: { bitbucket: ['snippet:write', 'issue:write'] }, + }, + }), + ).resolves.toMatchObject({ + token: 'account team pullrequest snippet issue snippet:write issue:write', + }); + }); + it('should handle host option', () => { const mockAuthApi = { getAccessToken: jest.fn(), diff --git a/packages/integration-react/src/api/ScmAuth.ts b/packages/integration-react/src/api/ScmAuth.ts index be3216962d..071374e6cb 100644 --- a/packages/integration-react/src/api/ScmAuth.ts +++ b/packages/integration-react/src/api/ScmAuth.ts @@ -15,6 +15,7 @@ */ import { + bitbucketAuthApiRef, createApiFactory, githubAuthApiRef, gitlabAuthApiRef, @@ -35,6 +36,9 @@ type ScopeMapping = { repoWrite: string[]; }; +// An enum of all supported providers +type ProviderName = 'generic' | 'github' | 'azure' | 'bitbucket' | 'gitlab'; + class ScmAuthMux implements ScmAuthApi { #providers: Array; @@ -93,12 +97,14 @@ export class ScmAuth implements ScmAuthApi { github: githubAuthApiRef, gitlab: gitlabAuthApiRef, azure: microsoftAuthApiRef, + bitbucket: bitbucketAuthApiRef, }, - factory: ({ github, gitlab, azure }) => + factory: ({ github, gitlab, azure, bitbucket }) => ScmAuth.merge( ScmAuth.forGithub(github), ScmAuth.forGitlab(gitlab), ScmAuth.forAzure(azure), + ScmAuth.forBitbucket(bitbucket), ), }); } @@ -116,7 +122,7 @@ export class ScmAuth implements ScmAuthApi { }; }, ): ScmAuth { - return new ScmAuth(authApi, options.host, options.scopeMapping); + return new ScmAuth('generic', authApi, options.host, options.scopeMapping); } /** @@ -139,7 +145,7 @@ export class ScmAuth implements ScmAuthApi { }, ): ScmAuth { const host = options?.host ?? 'github.com'; - return new ScmAuth(githubAuthApi, host, { + return new ScmAuth('github', githubAuthApi, host, { default: ['repo', 'read:org', 'read:user'], repoWrite: ['gist'], }); @@ -165,7 +171,7 @@ export class ScmAuth implements ScmAuthApi { }, ): ScmAuth { const host = options?.host ?? 'gitlab.com'; - return new ScmAuth(gitlabAuthApi, host, { + return new ScmAuth('gitlab', gitlabAuthApi, host, { default: ['read_user', 'read_api', 'read_repository'], repoWrite: ['write_repository', 'api'], }); @@ -191,7 +197,7 @@ export class ScmAuth implements ScmAuthApi { }, ): ScmAuth { const host = options?.host ?? 'dev.azure.com'; - return new ScmAuth(microsoftAuthApi, host, { + return new ScmAuth('azure', microsoftAuthApi, host, { default: [ 'vso.build', 'vso.code', @@ -223,7 +229,7 @@ export class ScmAuth implements ScmAuthApi { }, ): ScmAuth { const host = options?.host ?? 'bitbucket.org'; - return new ScmAuth(bitbucketAuthApi, host, { + return new ScmAuth('bitbucket', bitbucketAuthApi, host, { default: ['account', 'team', 'pullrequest', 'snippet', 'issue'], repoWrite: ['pullrequest:write', 'snippet:write', 'issue:write'], }); @@ -240,11 +246,18 @@ export class ScmAuth implements ScmAuthApi { #api: OAuthApi; #host: string; #scopeMapping: ScopeMapping; + #providerName: ProviderName; - private constructor(api: OAuthApi, host: string, scopeMapping: ScopeMapping) { + private constructor( + providerName: ProviderName, + api: OAuthApi, + host: string, + scopeMapping: ScopeMapping, + ) { this.#api = api; this.#host = host; this.#scopeMapping = scopeMapping; + this.#providerName = providerName; } /** @@ -254,6 +267,16 @@ export class ScmAuth implements ScmAuthApi { return url.host === this.#host; } + private getAdditionalScopesForProvider( + additionalScopes: ScmAuthTokenOptions['additionalScope'], + ): string[] { + if (!additionalScopes?.customScopes || this.#providerName === 'generic') { + return []; + } + + return additionalScopes.customScopes?.[this.#providerName] ?? []; + } + /** * Fetches credentials for the given resource. */ @@ -267,7 +290,17 @@ export class ScmAuth implements ScmAuthApi { scopes.push(...this.#scopeMapping.repoWrite); } - const token = await this.#api.getAccessToken(scopes, restOptions); + const additionalScopes = + this.getAdditionalScopesForProvider(additionalScope); + + if (additionalScopes.length) { + scopes.push(...additionalScopes); + } + + const uniqueScopes = [...new Set(scopes)]; + + const token = await this.#api.getAccessToken(uniqueScopes, restOptions); + return { token, headers: { diff --git a/packages/integration-react/src/api/ScmAuthApi.ts b/packages/integration-react/src/api/ScmAuthApi.ts index 44b007c4a2..be65842bc1 100644 --- a/packages/integration-react/src/api/ScmAuthApi.ts +++ b/packages/integration-react/src/api/ScmAuthApi.ts @@ -44,6 +44,16 @@ export interface ScmAuthTokenOptions extends AuthRequestOptions { * the ability to create things like issues and pull requests. */ repoWrite?: boolean; + /** + * Allow an arbitrary list of scopes provided from the user + * to request from the provider. + */ + customScopes?: { + github?: string[]; + azure?: string[]; + bitbucket?: string[]; + gitlab?: string[]; + }; }; } diff --git a/packages/integration/package.json b/packages/integration/package.json index 10400d791f..41ed3f08c3 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -39,9 +39,9 @@ "lodash": "^4.17.21" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1-next.1", "@backstage/config-loader": "^0.9.3", - "@backstage/test-utils": "^0.2.3", + "@backstage/test-utils": "^0.2.4-next.0", "@types/jest": "^26.0.7", "@types/luxon": "^2.0.4", "msw": "^0.35.0" diff --git a/packages/search-common/package.json b/packages/search-common/package.json index 1023158754..b574675724 100644 --- a/packages/search-common/package.json +++ b/packages/search-common/package.json @@ -40,7 +40,7 @@ "@backstage/plugin-permission-common": "^0.4.0-next.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0" + "@backstage/cli": "^0.13.1-next.0" }, "jest": { "roots": [ diff --git a/packages/embedded-techdocs-app/.eslintrc.js b/packages/techdocs-cli-embedded-app/.eslintrc.js similarity index 100% rename from packages/embedded-techdocs-app/.eslintrc.js rename to packages/techdocs-cli-embedded-app/.eslintrc.js diff --git a/packages/embedded-techdocs-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md similarity index 74% rename from packages/embedded-techdocs-app/CHANGELOG.md rename to packages/techdocs-cli-embedded-app/CHANGELOG.md index d8039973e7..27c4719686 100644 --- a/packages/embedded-techdocs-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,4 +1,43 @@ -# embedded-techdocs-app +# techdocs-cli-embedded-app + +## 0.2.62 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/cli@0.13.1 + - @backstage/integration-react@0.1.20 + - @backstage/core-app-api@0.5.2 + - @backstage/plugin-techdocs@0.13.2 + - @backstage/app-defaults@0.1.6 + - @backstage/test-utils@0.2.4 + - @backstage/plugin-catalog@0.7.11 + +## 0.2.62-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/cli@0.13.1-next.1 + - @backstage/core-app-api@0.5.2-next.0 + - @backstage/plugin-techdocs@0.13.2-next.1 + - @backstage/plugin-catalog@0.7.11-next.1 + - @backstage/app-defaults@0.1.6-next.1 + - @backstage/test-utils@0.2.4-next.0 + +## 0.2.62-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-techdocs@0.13.2-next.0 + - @backstage/cli@0.13.1-next.0 + - @backstage/app-defaults@0.1.6-next.0 + - @backstage/integration-react@0.1.20-next.0 + - @backstage/plugin-catalog@0.7.11-next.0 ## 0.2.61 diff --git a/packages/embedded-techdocs-app/app-config.dev.yaml b/packages/techdocs-cli-embedded-app/app-config.dev.yaml similarity index 100% rename from packages/embedded-techdocs-app/app-config.dev.yaml rename to packages/techdocs-cli-embedded-app/app-config.dev.yaml diff --git a/packages/embedded-techdocs-app/app-config.yaml b/packages/techdocs-cli-embedded-app/app-config.yaml similarity index 100% rename from packages/embedded-techdocs-app/app-config.yaml rename to packages/techdocs-cli-embedded-app/app-config.yaml diff --git a/packages/embedded-techdocs-app/cypress.json b/packages/techdocs-cli-embedded-app/cypress.json similarity index 100% rename from packages/embedded-techdocs-app/cypress.json rename to packages/techdocs-cli-embedded-app/cypress.json diff --git a/packages/embedded-techdocs-app/cypress/.eslintrc.json b/packages/techdocs-cli-embedded-app/cypress/.eslintrc.json similarity index 100% rename from packages/embedded-techdocs-app/cypress/.eslintrc.json rename to packages/techdocs-cli-embedded-app/cypress/.eslintrc.json diff --git a/packages/embedded-techdocs-app/cypress/integration/app.js b/packages/techdocs-cli-embedded-app/cypress/integration/app.js similarity index 100% rename from packages/embedded-techdocs-app/cypress/integration/app.js rename to packages/techdocs-cli-embedded-app/cypress/integration/app.js diff --git a/packages/embedded-techdocs-app/package.json b/packages/techdocs-cli-embedded-app/package.json similarity index 80% rename from packages/embedded-techdocs-app/package.json rename to packages/techdocs-cli-embedded-app/package.json index b9dab2d19e..6eb3119b06 100644 --- a/packages/embedded-techdocs-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,20 +1,20 @@ { - "name": "embedded-techdocs-app", - "version": "0.2.61", + "name": "techdocs-cli-embedded-app", + "version": "0.2.62", "private": true, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^0.1.5", + "@backstage/app-defaults": "^0.1.6", "@backstage/catalog-model": "^0.9.10", - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@backstage/config": "^0.1.13", - "@backstage/core-app-api": "^0.5.1", - "@backstage/core-components": "^0.8.6", + "@backstage/core-app-api": "^0.5.2", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/integration-react": "^0.1.19", - "@backstage/plugin-catalog": "^0.7.10", - "@backstage/plugin-techdocs": "^0.13.1", - "@backstage/test-utils": "^0.2.3", + "@backstage/integration-react": "^0.1.20", + "@backstage/plugin-catalog": "^0.7.11", + "@backstage/plugin-techdocs": "^0.13.2", + "@backstage/test-utils": "^0.2.4", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -26,7 +26,7 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/embedded-techdocs-app/public/android-chrome-192x192.png b/packages/techdocs-cli-embedded-app/public/android-chrome-192x192.png similarity index 100% rename from packages/embedded-techdocs-app/public/android-chrome-192x192.png rename to packages/techdocs-cli-embedded-app/public/android-chrome-192x192.png diff --git a/packages/embedded-techdocs-app/public/apple-touch-icon.png b/packages/techdocs-cli-embedded-app/public/apple-touch-icon.png similarity index 100% rename from packages/embedded-techdocs-app/public/apple-touch-icon.png rename to packages/techdocs-cli-embedded-app/public/apple-touch-icon.png diff --git a/packages/embedded-techdocs-app/public/favicon-16x16.png b/packages/techdocs-cli-embedded-app/public/favicon-16x16.png similarity index 100% rename from packages/embedded-techdocs-app/public/favicon-16x16.png rename to packages/techdocs-cli-embedded-app/public/favicon-16x16.png diff --git a/packages/embedded-techdocs-app/public/favicon-32x32.png b/packages/techdocs-cli-embedded-app/public/favicon-32x32.png similarity index 100% rename from packages/embedded-techdocs-app/public/favicon-32x32.png rename to packages/techdocs-cli-embedded-app/public/favicon-32x32.png diff --git a/packages/embedded-techdocs-app/public/favicon.ico b/packages/techdocs-cli-embedded-app/public/favicon.ico similarity index 100% rename from packages/embedded-techdocs-app/public/favicon.ico rename to packages/techdocs-cli-embedded-app/public/favicon.ico diff --git a/packages/embedded-techdocs-app/public/index.html b/packages/techdocs-cli-embedded-app/public/index.html similarity index 100% rename from packages/embedded-techdocs-app/public/index.html rename to packages/techdocs-cli-embedded-app/public/index.html diff --git a/packages/embedded-techdocs-app/public/manifest.json b/packages/techdocs-cli-embedded-app/public/manifest.json similarity index 100% rename from packages/embedded-techdocs-app/public/manifest.json rename to packages/techdocs-cli-embedded-app/public/manifest.json diff --git a/packages/embedded-techdocs-app/public/robots.txt b/packages/techdocs-cli-embedded-app/public/robots.txt similarity index 100% rename from packages/embedded-techdocs-app/public/robots.txt rename to packages/techdocs-cli-embedded-app/public/robots.txt diff --git a/packages/embedded-techdocs-app/public/safari-pinned-tab.svg b/packages/techdocs-cli-embedded-app/public/safari-pinned-tab.svg similarity index 100% rename from packages/embedded-techdocs-app/public/safari-pinned-tab.svg rename to packages/techdocs-cli-embedded-app/public/safari-pinned-tab.svg diff --git a/packages/embedded-techdocs-app/src/App.test.tsx b/packages/techdocs-cli-embedded-app/src/App.test.tsx similarity index 100% rename from packages/embedded-techdocs-app/src/App.test.tsx rename to packages/techdocs-cli-embedded-app/src/App.test.tsx diff --git a/packages/embedded-techdocs-app/src/App.tsx b/packages/techdocs-cli-embedded-app/src/App.tsx similarity index 100% rename from packages/embedded-techdocs-app/src/App.tsx rename to packages/techdocs-cli-embedded-app/src/App.tsx diff --git a/packages/embedded-techdocs-app/src/apis.ts b/packages/techdocs-cli-embedded-app/src/apis.ts similarity index 100% rename from packages/embedded-techdocs-app/src/apis.ts rename to packages/techdocs-cli-embedded-app/src/apis.ts diff --git a/packages/embedded-techdocs-app/src/components/Root/LogoFull.tsx b/packages/techdocs-cli-embedded-app/src/components/Root/LogoFull.tsx similarity index 100% rename from packages/embedded-techdocs-app/src/components/Root/LogoFull.tsx rename to packages/techdocs-cli-embedded-app/src/components/Root/LogoFull.tsx diff --git a/packages/embedded-techdocs-app/src/components/Root/LogoIcon.tsx b/packages/techdocs-cli-embedded-app/src/components/Root/LogoIcon.tsx similarity index 100% rename from packages/embedded-techdocs-app/src/components/Root/LogoIcon.tsx rename to packages/techdocs-cli-embedded-app/src/components/Root/LogoIcon.tsx diff --git a/packages/embedded-techdocs-app/src/components/Root/Root.tsx b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx similarity index 100% rename from packages/embedded-techdocs-app/src/components/Root/Root.tsx rename to packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx diff --git a/packages/embedded-techdocs-app/src/components/Root/index.ts b/packages/techdocs-cli-embedded-app/src/components/Root/index.ts similarity index 100% rename from packages/embedded-techdocs-app/src/components/Root/index.ts rename to packages/techdocs-cli-embedded-app/src/components/Root/index.ts diff --git a/packages/embedded-techdocs-app/src/components/TechDocsPage/TechDocsPage.tsx b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx similarity index 100% rename from packages/embedded-techdocs-app/src/components/TechDocsPage/TechDocsPage.tsx rename to packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx diff --git a/packages/embedded-techdocs-app/src/components/TechDocsPage/index.ts b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/index.ts similarity index 100% rename from packages/embedded-techdocs-app/src/components/TechDocsPage/index.ts rename to packages/techdocs-cli-embedded-app/src/components/TechDocsPage/index.ts diff --git a/packages/embedded-techdocs-app/src/index.tsx b/packages/techdocs-cli-embedded-app/src/index.tsx similarity index 100% rename from packages/embedded-techdocs-app/src/index.tsx rename to packages/techdocs-cli-embedded-app/src/index.tsx diff --git a/packages/embedded-techdocs-app/src/plugins.ts b/packages/techdocs-cli-embedded-app/src/plugins.ts similarity index 100% rename from packages/embedded-techdocs-app/src/plugins.ts rename to packages/techdocs-cli-embedded-app/src/plugins.ts diff --git a/packages/embedded-techdocs-app/src/setupTests.ts b/packages/techdocs-cli-embedded-app/src/setupTests.ts similarity index 100% rename from packages/embedded-techdocs-app/src/setupTests.ts rename to packages/techdocs-cli-embedded-app/src/setupTests.ts diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index c261e83731..f68fc44101 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,28 @@ # @techdocs/cli +## 0.8.12 + +### Patch Changes + +- 14472509a3: Use a local file dependency for techdocs-cli-embedded-app, to ensure that it's always pulled out of the workspace +- Updated dependencies + - @backstage/backend-common@0.10.6 + - @backstage/techdocs-common@0.11.6 + +## 0.8.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + - @backstage/techdocs-common@0.11.6-next.0 + +## 0.8.12-next.0 + +### Patch Changes + +- 14472509a3: Use a local file dependency for techdocs-cli-embedded-app, to ensure that it's always pulled out of the workspace + ## 0.8.11 ### Patch Changes @@ -138,10 +161,10 @@ #### :house: Internal - - `embedded-techdocs-app` - - [#122](https://github.com/backstage/techdocs-cli/pull/122) chore(deps-dev): bump @types/node from 12.20.20 to 16.7.1 in /packages/embedded-techdocs-app ([@dependabot[bot]](https://github.com/apps/dependabot)) - - [#120](https://github.com/backstage/techdocs-cli/pull/120) chore(deps-dev): bump @types/react-dom from 16.9.14 to 17.0.9 in /packages/embedded-techdocs-app ([@dependabot[bot]](https://github.com/apps/dependabot)) - - [#119](https://github.com/backstage/techdocs-cli/pull/119) chore(deps-dev): bump @testing-library/user-event from 12.8.3 to 13.2.1 in /packages/embedded-techdocs-app ([@dependabot[bot]](https://github.com/apps/dependabot)) + - `techdocs-cli-embedded-app` + - [#122](https://github.com/backstage/techdocs-cli/pull/122) chore(deps-dev): bump @types/node from 12.20.20 to 16.7.1 in /packages/techdocs-cli-embedded-app ([@dependabot[bot]](https://github.com/apps/dependabot)) + - [#120](https://github.com/backstage/techdocs-cli/pull/120) chore(deps-dev): bump @types/react-dom from 16.9.14 to 17.0.9 in /packages/techdocs-cli-embedded-app ([@dependabot[bot]](https://github.com/apps/dependabot)) + - [#119](https://github.com/backstage/techdocs-cli/pull/119) chore(deps-dev): bump @testing-library/user-event from 12.8.3 to 13.2.1 in /packages/techdocs-cli-embedded-app ([@dependabot[bot]](https://github.com/apps/dependabot)) - [#118](https://github.com/backstage/techdocs-cli/pull/118) chore(deps-dev): bump @testing-library/react from 10.4.9 to 12.0.0 ([@dependabot[bot]](https://github.com/apps/dependabot)) - Other - [#117](https://github.com/backstage/techdocs-cli/pull/117) chore(deps): bump @backstage/plugin-catalog from 0.6.11 to 0.6.12 ([@dependabot[bot]](https://github.com/apps/dependabot)) diff --git a/packages/techdocs-cli/README.md b/packages/techdocs-cli/README.md index 6f070147eb..3ac423cb38 100644 --- a/packages/techdocs-cli/README.md +++ b/packages/techdocs-cli/README.md @@ -8,7 +8,7 @@ See [techdocs-cli usage docs](https://backstage.io/docs/features/techdocs/cli). ## Development -NOTE: When we build `techdocs-cli` it copies the output `embedded-techdocs-app` +NOTE: When we build `techdocs-cli` it copies the output `techdocs-cli-embedded-app` bundle into the `packages/techdocs-cli/dist` which is then published with the `@techdocs/cli` npm package. @@ -16,7 +16,7 @@ bundle into the `packages/techdocs-cli/dist` which is then published with the ```sh # From the root of this repository run -# NOTE: This will build the embedded-techdocs-app and copy the output into the cli dist directory +# NOTE: This will build the techdocs-cli-embedded-app and copy the output into the cli dist directory yarn build --scope @techdocs/cli # Now execute the binary @@ -26,14 +26,14 @@ packages/techdocs-cli/bin/techdocs-cli export PATH=/path/to/backstage/packages/techdocs-cli/bin:$PATH ``` -If you want to test live test changes to the `packages/embedded-techdocs-app` +If you want to test live test changes to the `packages/techdocs-cli-embedded-app` you can serve the app and run the CLI using the following commands: ```sh -# Open a shell to the embedded-techdocs-app directory -cd packages/embedded-techdocs-app +# Open a shell to the techdocs-cli-embedded-app directory +cd packages/techdocs-cli-embedded-app -# Run the embedded-techdocs-app using dev mode +# Run the techdocs-cli-embedded-app using dev mode yarn start # In another shell use the techdocs-cli from the root of this repo diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index c1e150b7e3..a8dab88c55 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "0.8.11", + "version": "0.8.12", "private": false, "publishConfig": { "access": "public" @@ -32,7 +32,7 @@ "techdocs-cli": "bin/techdocs-cli" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/commander": "^2.12.2", "@types/fs-extra": "^9.0.6", "@types/http-proxy": "^1.17.4", @@ -40,7 +40,7 @@ "@types/node": "^14.14.32", "@types/serve-handler": "^6.1.0", "@types/webpack-env": "^1.15.3", - "embedded-techdocs-app": "file:../embedded-techdocs-app", + "techdocs-cli-embedded-app": "file:../techdocs-cli-embedded-app", "find-process": "^1.4.5", "nodemon": "^2.0.2", "ts-node": "^10.0.0" @@ -55,10 +55,10 @@ "ext": "ts" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", - "@backstage/techdocs-common": "^0.11.5", + "@backstage/techdocs-common": "^0.11.6", "@types/dockerode": "^3.3.0", "commander": "^6.1.0", "dockerode": "^3.3.1", diff --git a/packages/techdocs-cli/scripts/build.sh b/packages/techdocs-cli/scripts/build.sh index 630fef42f0..31c2eae6c3 100755 --- a/packages/techdocs-cli/scripts/build.sh +++ b/packages/techdocs-cli/scripts/build.sh @@ -18,7 +18,7 @@ set -e SCRIPT_DIR=$(dirname $0) TECHDOCS_CLI_DIR="$SCRIPT_DIR"/.. -TECHDOCS_CLI_EMBEDDED_APP_DIR="$TECHDOCS_CLI_DIR"/../embedded-techdocs-app +TECHDOCS_CLI_EMBEDDED_APP_DIR="$TECHDOCS_CLI_DIR"/../techdocs-cli-embedded-app compile_and_build_cli() { echo "đź“„ Compiling..." @@ -32,9 +32,9 @@ compile_and_build_cli() { build_and_embed_app() { echo "đźšš Embedding app..." if [ "$TECHDOCS_CLI_DEV_MODE" = "true" ] ; then - yarn workspace embedded-techdocs-app build:dev > /dev/null + yarn workspace techdocs-cli-embedded-app build:dev > /dev/null else - yarn workspace embedded-techdocs-app build > /dev/null + yarn workspace techdocs-cli-embedded-app build > /dev/null fi cp -r "$TECHDOCS_CLI_EMBEDDED_APP_DIR"/dist "$TECHDOCS_CLI_DIR"/dist/techdocs-preview-bundle > /dev/null } diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 9a3fa5051b..d03ddb6101 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/techdocs-common +## 0.11.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.11.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + ## 0.11.5 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index a82dda2eae..94a87a41e8 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.11.5", + "version": "0.11.6", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -38,7 +38,7 @@ "dependencies": { "@azure/identity": "^2.0.1", "@azure/storage-blob": "^12.5.0", - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/fs-extra": "^9.0.5", "@types/js-yaml": "^4.0.0", "@types/mime-types": "^2.1.0", diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 1867b52265..35c9ebfb35 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/test-utils +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@0.5.2 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@0.5.2-next.0 + ## 0.2.3 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index e396a47917..c5fe18d7f7 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.2.3", + "version": "0.2.4", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.13", - "@backstage/core-app-api": "^0.5.1", + "@backstage/core-app-api": "^0.5.2", "@backstage/core-plugin-api": "^0.6.0", "@backstage/plugin-permission-common": "^0.4.0", "@backstage/plugin-permission-react": "^0.3.0", @@ -51,7 +51,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "msw": "^0.35.0" diff --git a/packages/theme/package.json b/packages/theme/package.json index cd7517b2d8..517a517ba7 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -31,7 +31,7 @@ "@material-ui/core": "^4.12.2" }, "devDependencies": { - "@backstage/cli": "^0.13.0" + "@backstage/cli": "^0.13.1-next.0" }, "files": [ "dist" diff --git a/packages/types/package.json b/packages/types/package.json index 881e30081f..0a0f78ca3d 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -31,7 +31,7 @@ }, "dependencies": {}, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1-next.0", "@types/zen-observable": "^0.8.0", "zen-observable": "^0.8.15" }, diff --git a/packages/version-bridge/package.json b/packages/version-bridge/package.json index ce5456bea8..7b841704d5 100644 --- a/packages/version-bridge/package.json +++ b/packages/version-bridge/package.json @@ -33,7 +33,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2" diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md index 36d322a0e0..489559c023 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-airbrake +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index e037e38009..dcd60e3687 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", @@ -34,11 +34,11 @@ }, "devDependencies": { "@types/object-hash": "^2.2.1", - "@backstage/app-defaults": "^0.1.5", - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/app-defaults": "^0.1.6", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md index 290838ac3d..f0fa792278 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-allure +## 0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.1.12 ### Patch Changes diff --git a/plugins/allure/package.json b/plugins/allure/package.json index 4d8768dc03..4b497bd56e 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-allure", "description": "A Backstage plugin that integrates with Allure", - "version": "0.1.12", + "version": "0.1.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,9 +23,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -37,10 +37,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md index 8b82c050d3..81c61769fb 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-analytics-module-ga +## 0.1.8 + +### Patch Changes + +- b40a0ccc4d: Added the ability to capture and set user IDs from Backstage's `identityApi`. For full instructions on how to + set this up, see [the User ID section of its README](https://github.com/backstage/backstage/tree/master/plugins/analytics-module-ga#user-ids) +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.1.8-next.0 + +### Patch Changes + +- b40a0ccc4d: Added the ability to capture and set user IDs from Backstage's `identityApi`. For full instructions on how to + set this up, see [the User ID section of its README](https://github.com/backstage/backstage/tree/master/plugins/analytics-module-ga#user-ids) +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/analytics-module-ga/README.md b/plugins/analytics-module-ga/README.md index a0c4fe517a..92cfd7e9ae 100644 --- a/plugins/analytics-module-ga/README.md +++ b/plugins/analytics-module-ga/README.md @@ -14,15 +14,22 @@ This plugin contains no other functionality. ```tsx // packages/app/src/apis.ts -import { analyticsApiRef, configApiRef } from '@backstage/core-plugin-api'; +import { + analyticsApiRef, + configApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; import { GoogleAnalytics } from '@backstage/plugin-analytics-module-ga'; export const apis: AnyApiFactory[] = [ // Instantiate and register the GA Analytics API Implementation. createApiFactory({ api: analyticsApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => GoogleAnalytics.fromConfig(configApi), + deps: { configApi: configApiRef, identityApi: identityApiRef }, + factory: ({ configApi, identityApi }) => + GoogleAnalytics.fromConfig(configApi, { + identityApi, + }), }), ]; ``` @@ -92,6 +99,63 @@ app: key: someEventContextAttr ``` +### User IDs + +This plugin supports accurately deriving user-oriented metrics (like monthly +active users) using Google Analytics' [user ID views][ga-user-id-view]. To +enable this... + +1. Be sure you've gone through the process of setting up a user ID view in your + Backstage instance's Google Analytics property (see docs linked above). +2. Make sure you instantiate `GoogleAnalytics` with an `identityApi` instance + passed to it, as shown in the installation section above. +3. Set `app.analytics.ga.identity` to either `required` or `optional` in your + `app.config.yaml`, like this: + + ```yaml + app: + analytics: + ga: + trackingId: UA-0000000-0 + identity: optional + ``` + + Set `identity` to `optional` if you need accurate session counts, including + cases where users do not sign in at all. Use `required` if you need all hits + to be associated with a user ID without exception (and don't mind if some + sessions are not captured, such as those where no sign in occur). + +Note that, to comply with GA policies, the value of the User ID is +pseudonymized before being sent to GA. By default, it is a `sha256` hash of the +current user's `userEntityRef` as returned by the `identityApi`. To set a +different value, provide a `userIdTransform` function alongside `identityApi` +when you instantiate `GoogleAnalytics`. This function will be passed the +`userEntityRef` as an argument and should resolve to the value you wish to set +as the user ID. For example: + +```typescript +import { + analyticsApiRef, + configApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { GoogleAnalytics } from '@backstage/plugin-analytics-module-ga'; + +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: analyticsApiRef, + deps: { configApi: configApiRef, identityApi: identityApiRef }, + factory: ({ configApi, identityApi }) => + GoogleAnalytics.fromConfig(configApi, { + identityApi, + userIdTransform: async (userEntityRef: string): Promise => { + return customHashingFunction(userEntityRef); + }, + }), + }), +]; +``` + ### Debugging and Testing In pre-production environments, you may wish to set additional configurations @@ -147,3 +211,4 @@ app: [what-is-a-custom-dimension]: https://support.google.com/analytics/answer/2709828 [configure-custom-dimension]: https://support.google.com/analytics/answer/2709828#configuration +[ga-user-id-view]: https://support.google.com/analytics/answer/3123669 diff --git a/plugins/analytics-module-ga/api-report.md b/plugins/analytics-module-ga/api-report.md index b0f3b736c3..9a359f81e9 100644 --- a/plugins/analytics-module-ga/api-report.md +++ b/plugins/analytics-module-ga/api-report.md @@ -7,18 +7,23 @@ import { AnalyticsApi } from '@backstage/core-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Config } from '@backstage/config'; +import { IdentityApi } from '@backstage/core-plugin-api'; -// Warning: (ae-missing-release-tag) "analyticsModuleGA" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public @deprecated (undocumented) export const analyticsModuleGA: BackstagePlugin<{}, {}>; -// Warning: (ae-missing-release-tag) "GoogleAnalytics" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class GoogleAnalytics implements AnalyticsApi { captureEvent(event: AnalyticsEvent): void; - static fromConfig(config: Config): GoogleAnalytics; + static fromConfig( + config: Config, + options?: { + identityApi?: IdentityApi; + userIdTransform?: + | 'sha-256' + | ((userEntityRef: string) => Promise); + }, + ): GoogleAnalytics; } // (No @packageDocumentation comment for this package) diff --git a/plugins/analytics-module-ga/config.d.ts b/plugins/analytics-module-ga/config.d.ts index ac534daae8..b91f5812c1 100644 --- a/plugins/analytics-module-ga/config.d.ts +++ b/plugins/analytics-module-ga/config.d.ts @@ -34,6 +34,25 @@ export interface Config { */ scriptSrc?: string; + /** + * Controls how the identityApi is used when sending data to GA: + * + * - `disabled`: (Default) Explicitly prevents a user's identity from + * being used when capturing events in GA. + * - `optional`: Pageviews and hits are forwarded to GA as they happen + * and only include user identity metadata once known. Guarantees + * that hits are captured for all sessions, even if no sign in + * occurs, but may result in dropped hits in User ID views. + * - `required`: All pageviews and hits are deferred until an identity + * is known. Guarantees that all data sent to GA correlates to a user + * identity, but prevents GA from receiving events for sessions in + * which a user does not sign in. An `identityApi` instance must be + * passed during instantiation when set to this value. + * + * @visibility frontend + */ + identity?: 'disabled' | 'optional' | 'required'; + /** * Whether or not to log analytics debug statements to the console. * Defaults to false. diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 255a0cae8e..bbc9ffcd2e 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-ga", - "version": "0.1.7", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", @@ -35,10 +35,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts index cbcf95cb55..a4f7195fec 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts @@ -15,10 +15,17 @@ */ import { ConfigReader } from '@backstage/config'; +import { IdentityApi } from '@backstage/core-plugin-api'; import ReactGA from 'react-ga'; import { GoogleAnalytics } from './GoogleAnalytics'; describe('GoogleAnalytics', () => { + const context = { + extension: 'App', + pluginId: 'some-plugin', + routeRef: 'unknown', + releaseNum: 1337, + }; const trackingId = 'UA-000000-0'; const basicValidConfig = new ConfigReader({ app: { analytics: { ga: { trackingId, testMode: true } } }, @@ -50,12 +57,6 @@ describe('GoogleAnalytics', () => { }); describe('integration', () => { - const context = { - extension: 'App', - pluginId: 'some-plugin', - routeRef: 'unknown', - releaseNum: 1337, - }; const advancedConfig = new ConfigReader({ app: { analytics: { @@ -141,20 +142,13 @@ describe('GoogleAnalytics', () => { context, }); - // Expect a set command first. - const [setCommand, setData] = ReactGA.testModeAPI.calls[1]; - expect(setCommand).toBe('set'); - expect(setData).toMatchObject({ - dimension1: context.pluginId, - metric1: context.releaseNum, - }); - - // Followed by a send command. - const [sendCommand, sendData] = ReactGA.testModeAPI.calls[2]; - expect(sendCommand).toBe('send'); - expect(sendData).toMatchObject({ + const [command, data] = ReactGA.testModeAPI.calls[1]; + expect(command).toBe('send'); + expect(data).toMatchObject({ hitType: 'pageview', page: '/a-page', + dimension1: context.pluginId, + metric1: context.releaseNum, }); }); @@ -208,4 +202,214 @@ describe('GoogleAnalytics', () => { }); }); }); + + describe('identityApi', () => { + const identityApi = { + getBackstageIdentity: jest.fn().mockResolvedValue({ + userEntityRef: 'User:default/someone', + }), + } as unknown as IdentityApi; + + it('does not set userId unless explicitly configured', async () => { + // Instantiate with identityApi and default configs. + const api = GoogleAnalytics.fromConfig(basicValidConfig, { identityApi }); + api.captureEvent({ + action: 'navigate', + subject: '/', + context, + }); + + // Wait for any/all promises involved to settle. + await new Promise(resolve => setImmediate(resolve)); + + // A pageview should have been fired immediately. + const [command, data] = ReactGA.testModeAPI.calls[1]; + expect(command).toBe('send'); + expect(data).toMatchObject({ + hitType: 'pageview', + page: '/', + }); + + // There should not have been a UserID set. + expect(ReactGA.testModeAPI.calls).toHaveLength(2); + }); + + it('sets hashed userId when identityApi is provided', async () => { + // Instantiate with identityApi and identity set to optional + const optionalConfig = new ConfigReader({ + app: { + analytics: { + ga: { trackingId, testMode: true, identity: 'optional' }, + }, + }, + }); + const api = GoogleAnalytics.fromConfig(optionalConfig, { identityApi }); + api.captureEvent({ + action: 'navigate', + subject: '/', + context, + }); + + // Wait for any/all promises involved to settle. + await new Promise(resolve => setImmediate(resolve)); + + // A pageview should have been fired immediately. + const [command, data] = ReactGA.testModeAPI.calls[1]; + expect(command).toBe('send'); + expect(data).toMatchObject({ + hitType: 'pageview', + page: '/', + }); + + // User ID should have been set after the pageview. + const [setCommand, setData] = ReactGA.testModeAPI.calls[2]; + expect(setCommand).toBe('set'); + expect(setData).toMatchObject({ + // String indicating userEntityRef went through expected hashing. + userId: '557365723a64656661756c742f736f6d656f6e65', + }); + }); + + it('set custom-hashed userId when userIdTransform is provided', async () => { + const userIdTransform = jest.fn().mockResolvedValue('s0m3hash3dvalu3'); + const optionalConfig = new ConfigReader({ + app: { + analytics: { + ga: { trackingId, testMode: true, identity: 'optional' }, + }, + }, + }); + const api = GoogleAnalytics.fromConfig(optionalConfig, { + identityApi, + userIdTransform, + }); + api.captureEvent({ + action: 'navigate', + subject: '/', + context, + }); + + // Wait for any/all promises involved to settle. + await new Promise(resolve => setImmediate(resolve)); + + // User ID should have been set after the pageview. + const [setCommand, setData] = ReactGA.testModeAPI.calls[2]; + expect(setCommand).toBe('set'); + expect(setData).toMatchObject({ + userId: 's0m3hash3dvalu3', + }); + expect(userIdTransform).toHaveBeenCalledWith('User:default/someone'); + }); + + it('does not set userId when identityApi is provided and ga.identity is explicitly disabled', async () => { + // Instantiate with identityApi and identity explicitly disabled. + const disabledConfig = new ConfigReader({ + app: { + analytics: { + ga: { trackingId, testMode: true, identity: 'disabled' }, + }, + }, + }); + const api = GoogleAnalytics.fromConfig(disabledConfig, { identityApi }); + api.captureEvent({ + action: 'navigate', + subject: '/', + context, + }); + + // Wait for any/all promises involved to settle. + await new Promise(resolve => setImmediate(resolve)); + + // A pageview should have been fired immediately. + const [command, data] = ReactGA.testModeAPI.calls[1]; + expect(command).toBe('send'); + expect(data).toMatchObject({ + hitType: 'pageview', + page: '/', + }); + + // There should not have been a UserID set. + expect(ReactGA.testModeAPI.calls).toHaveLength(2); + }); + + it('throws error when ga.identity is required but no identityApi is provided', async () => { + // Instantiate without identityApi and identity explicitly disabled. + const requiredConfig = new ConfigReader({ + app: { + analytics: { + ga: { trackingId, testMode: true, identity: 'required' }, + }, + }, + }); + + expect(() => GoogleAnalytics.fromConfig(requiredConfig)).toThrow(); + }); + + it('defers event capture when ga.identity is required', async () => { + // Instantiate with identityApi and identity explicitly required. + const requiredConfig = new ConfigReader({ + app: { + analytics: { + ga: { trackingId, testMode: true, identity: 'required' }, + }, + }, + }); + const api = GoogleAnalytics.fromConfig(requiredConfig, { identityApi }); + + // Fire a pageview and an event. + api.captureEvent({ + action: 'navigate', + subject: '/', + context, + }); + api.captureEvent({ + action: 'test', + subject: 'some label', + context, + }); + + // Wait for any/all promises involved to settle. + await new Promise(resolve => setImmediate(resolve)); + + // User ID should have been set first. + const [setCommand, setData] = ReactGA.testModeAPI.calls[1]; + expect(setCommand).toBe('set'); + expect(setData).toMatchObject({ + // String indicating userEntityRef went through expected hashing. + userId: '557365723a64656661756c742f736f6d656f6e65', + }); + + // Then a pageview should have been fired with a queue time. + const [pageCommand, pageData] = ReactGA.testModeAPI.calls[2]; + expect(pageCommand).toBe('send'); + expect(pageData).toMatchObject({ + hitType: 'pageview', + page: '/', + queueTime: expect.any(Number), + }); + + // Then an event should have been fired with a queue time. + const [eventCommand, eventData] = ReactGA.testModeAPI.calls[3]; + expect(eventCommand).toBe('send'); + expect(eventData).toMatchObject({ + hitType: 'event', + queueTime: expect.any(Number), + }); + + // And subsequent hits should not have a queue time. + api.captureEvent({ + action: 'navigate', + subject: '/page-2', + context, + }); + + const [lastCommand, lastData] = ReactGA.testModeAPI.calls[4]; + expect(lastCommand).toBe('send'); + expect(lastData).toMatchObject({ + hitType: 'pageview', + page: '/page-2', + }); + expect(lastData.queueTime).toBeUndefined(); + }); + }); }); diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts index 28202607be..ad7205983c 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts @@ -20,8 +20,10 @@ import { AnalyticsContextValue, AnalyticsEventAttributes, AnalyticsEvent, + IdentityApi, } from '@backstage/core-plugin-api'; import { Config } from '@backstage/config'; +import { DeferredCapture } from '../../../util'; type CustomDimensionOrMetricConfig = { type: 'dimension' | 'metric'; @@ -32,21 +34,36 @@ type CustomDimensionOrMetricConfig = { /** * Google Analytics API provider for the Backstage Analytics API. + * @public */ export class GoogleAnalytics implements AnalyticsApi { private readonly cdmConfig: CustomDimensionOrMetricConfig[]; + private customUserIdTransform?: (userEntityRef: string) => Promise; + private readonly capture: DeferredCapture; /** * Instantiate the implementation and initialize ReactGA. */ private constructor(options: { + identityApi?: IdentityApi; + userIdTransform?: 'sha-256' | ((userEntityRef: string) => Promise); cdmConfig: CustomDimensionOrMetricConfig[]; + identity: string; trackingId: string; scriptSrc?: string; testMode: boolean; debug: boolean; }) { - const { cdmConfig, trackingId, scriptSrc, testMode, debug } = options; + const { + cdmConfig, + identity, + trackingId, + identityApi, + userIdTransform = 'sha-256', + scriptSrc, + testMode, + debug, + } = options; this.cdmConfig = cdmConfig; @@ -57,15 +74,37 @@ export class GoogleAnalytics implements AnalyticsApi { gaAddress: scriptSrc, titleCase: false, }); + + // If identity is required, defer event capture until identity is known. + this.capture = new DeferredCapture({ defer: identity === 'required' }); + + // Allow custom userId transformation. + this.customUserIdTransform = + typeof userIdTransform === 'function' ? userIdTransform : undefined; + + // Capture user only when explicitly enabled and provided. + if (identity !== 'disabled' && identityApi) { + this.setUserFrom(identityApi); + } } /** * Instantiate a fully configured GA Analytics API implementation. */ - static fromConfig(config: Config) { + static fromConfig( + config: Config, + options: { + identityApi?: IdentityApi; + userIdTransform?: + | 'sha-256' + | ((userEntityRef: string) => Promise); + } = {}, + ) { // Get all necessary configuration. const trackingId = config.getString('app.analytics.ga.trackingId'); const scriptSrc = config.getOptionalString('app.analytics.ga.scriptSrc'); + const identity = + config.getOptionalString('app.analytics.ga.identity') || 'disabled'; const debug = config.getOptionalBoolean('app.analytics.ga.debug') ?? false; const testMode = config.getOptionalBoolean('app.analytics.ga.testMode') ?? false; @@ -83,8 +122,16 @@ export class GoogleAnalytics implements AnalyticsApi { }; }) ?? []; + if (identity === 'required' && !options.identityApi) { + throw new Error( + 'Invalid config: identity API must be provided to deps when ga.identity is required', + ); + } + // Return an implementation instance. return new GoogleAnalytics({ + ...options, + identity, trackingId, scriptSrc, cdmConfig, @@ -103,16 +150,11 @@ export class GoogleAnalytics implements AnalyticsApi { const customMetadata = this.getCustomDimensionMetrics(context, attributes); if (action === 'navigate' && context.extension === 'App') { - // Set any/all custom dimensions. - if (Object.keys(customMetadata).length) { - ReactGA.set(customMetadata); - } - - ReactGA.pageview(subject); + this.capture.pageview(subject, customMetadata); return; } - ReactGA.event({ + this.capture.event({ category: context.extension || 'App', action, label: subject, @@ -151,4 +193,59 @@ export class GoogleAnalytics implements AnalyticsApi { return customDimensionsMetrics; } + + /** + * Sets the GA userId, based on the `userEntityRef` set on the backstage + * identity loaded from a given Backstage Identity API instance. Because + * Google forbids sending any PII (including on the userId field), we hash + * the entire `userEntityRef` on behalf of integrators: + * + * - With value `User:default/name`, userId becomes `sha256(User:default/name)` + * + * If an integrator wishes to use an alternative hashing mechanism or an + * entirely different value, they may do so by passing a `userIdTransform` + * function alongside the `identityApi` to `GoogleAnalytics.fromConfig()`. + * This function receives the `userEntityRef` as an argument and should + * resolve to a hashed version of whatever identifier they choose. + * + * Note: this feature requires that an integrator has set up a Google + * Analytics User ID view in the property used to track Backstage. + */ + private async setUserFrom(identityApi: IdentityApi) { + const { userEntityRef } = await identityApi.getBackstageIdentity(); + + // Prevent PII from being passed to Google Analytics. + const userId = await this.getPrivateUserId(userEntityRef); + + // Set the user ID. + ReactGA.set({ userId }); + + // Notify the deferred capture mechanism that it may proceed. + this.capture.setReady(); + } + + /** + * Returns a PII-free (according to Google's terms of service) user ID for + * use in Google Analytics. + */ + private getPrivateUserId(userEntityRef: string): Promise { + // Allow integrators to provide their own hashing transformer. + if (this.customUserIdTransform) { + return this.customUserIdTransform(userEntityRef); + } + + return this.hash(userEntityRef); + } + + /** + * Simple hash function; relies on web cryptography + the sha-256 algorithm. + */ + private async hash(value: string): Promise { + const digest = await crypto.subtle.digest( + 'sha-256', + new TextEncoder().encode(value), + ); + const hashArray = Array.from(new Uint8Array(digest)); + return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + } } diff --git a/plugins/analytics-module-ga/src/index.ts b/plugins/analytics-module-ga/src/index.ts index 17a3213c6d..fca8d91658 100644 --- a/plugins/analytics-module-ga/src/index.ts +++ b/plugins/analytics-module-ga/src/index.ts @@ -15,4 +15,4 @@ */ export { analyticsModuleGA } from './plugin'; -export { GoogleAnalytics } from './apis/implementations/AnalyticsApi'; +export * from './apis/implementations/AnalyticsApi'; diff --git a/plugins/analytics-module-ga/src/plugin.ts b/plugins/analytics-module-ga/src/plugin.ts index d63ae6616f..4eec63aa2e 100644 --- a/plugins/analytics-module-ga/src/plugin.ts +++ b/plugins/analytics-module-ga/src/plugin.ts @@ -15,6 +15,12 @@ */ import { createPlugin } from '@backstage/core-plugin-api'; +/** + * @deprecated Importing and including this plugin in an app has no effect. + * This will be removed in a future release. + * + * @public + */ export const analyticsModuleGA = createPlugin({ id: 'analytics-provider-ga', }); diff --git a/plugins/analytics-module-ga/src/setupTests.ts b/plugins/analytics-module-ga/src/setupTests.ts index fc6dbd98f8..4ed20ac097 100644 --- a/plugins/analytics-module-ga/src/setupTests.ts +++ b/plugins/analytics-module-ga/src/setupTests.ts @@ -15,3 +15,20 @@ */ import '@testing-library/jest-dom'; import 'cross-fetch/polyfill'; + +// eslint-disable-next-line no-restricted-imports +import { TextEncoder } from 'util'; + +// Mock browser crypto.subtle.digest method for sha-256 hashing. +Object.defineProperty(global.self, 'crypto', { + value: { + subtle: { + digest: (_algo: string, data: Uint8Array): ArrayBuffer => data.buffer, + }, + }, +}); + +// Also used in browser-based APIs for hashing. +Object.defineProperty(global.self, 'TextEncoder', { + value: TextEncoder, +}); diff --git a/plugins/analytics-module-ga/src/util/DeferredCapture.ts b/plugins/analytics-module-ga/src/util/DeferredCapture.ts new file mode 100644 index 0000000000..38afecd74d --- /dev/null +++ b/plugins/analytics-module-ga/src/util/DeferredCapture.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import ReactGA from 'react-ga'; + +type Hit = { + timestamp: number; + data: { + hitType: 'pageview' | 'event'; + [x: string]: any; + }; +}; + +/** + * A wrapper around ReactGA that can optionally handle latent capture logic. + * + * - When defer is `false`, event data is sent directly to GA. + * - When defer is `true`, event data is queued (with a timestamp), so that it + * can be sent to GA once externally indicated to be ready. This relies on + * the `qt` or `queueTime` parameter of the Measurement Protocol. + * + * @see https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#qt + */ +export class DeferredCapture { + /** + * Queue of deferred hits to be processed when ready. When undefined, hits + * can safely be sent without delay. + */ + private queue: Hit[] | undefined; + + constructor({ defer = false }: { defer: boolean }) { + this.queue = defer ? [] : undefined; + } + + /** + * Indicates that deferred capture may now proceed. + */ + setReady() { + if (this.queue) { + this.queue.forEach(this.sendDeferred); + this.queue = undefined; + } + } + + /** + * Either forwards the pageview directly to GA, or (if configured) enqueues + * the pageview hit to be captured when ready. + */ + pageview(path: string, metadata: ReactGA.FieldsObject = {}) { + if (this.queue) { + this.queue.push({ + timestamp: Date.now(), + data: { + hitType: 'pageview', + page: path, + ...metadata, + }, + }); + return; + } + + ReactGA.send({ + hitType: 'pageview', + page: path, + ...metadata, + }); + } + + /** + * Either forwards the event directly to GA, or (if configured) enqueues the + * event hit to be captured when ready. + */ + event(eventDetails: ReactGA.EventArgs) { + if (this.queue) { + this.queue.push({ + timestamp: Date.now(), + data: { + ...eventDetails, + hitType: 'event', + }, + }); + return; + } + + ReactGA.event(eventDetails); + } + + /** + * Sends a given hit to GA, decorated with the correct queue time. + */ + private sendDeferred(hit: Hit) { + // Send the hit with the appropriate queue time (`qt`). + ReactGA.send({ + ...hit.data, + queueTime: Date.now() - hit.timestamp, + }); + } +} diff --git a/plugins/analytics-module-ga/src/util/index.ts b/plugins/analytics-module-ga/src/util/index.ts new file mode 100644 index 0000000000..3b7a90c808 --- /dev/null +++ b/plugins/analytics-module-ga/src/util/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { DeferredCapture } from './DeferredCapture'; diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md index 4349718587..7407a9f0c9 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-apache-airflow +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.1.4 ### Patch Changes diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index 32fdd8b5cb..7b760070ec 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apache-airflow", - "version": "0.1.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -33,10 +33,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 5e495a99a8..cd477a6152 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-api-docs +## 0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/plugin-catalog@0.7.11 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog@0.7.11-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.7.0 ### Minor Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 08a1de6464..26f28ae40b 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-api-docs", "description": "A Backstage plugin that helps represent API entities in the frontend", - "version": "0.7.0", + "version": "0.7.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ "dependencies": { "@asyncapi/react-component": "1.0.0-next.32", "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog": "^0.7.10", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog": "^0.7.11", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 68a38461fe..b2a8d38636 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-app-backend +## 0.3.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.3.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + ## 0.3.22 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 9b9cfe01e7..361dc07b2c 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.22", + "version": "0.3.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config-loader": "^0.9.3", "@backstage/config": "^0.1.13", "@backstage/types": "^0.1.1", @@ -47,8 +47,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.15", - "@backstage/cli": "^0.13.0", + "@backstage/backend-test-utils": "^0.1.16", + "@backstage/cli": "^0.13.1", "@backstage/types": "^0.1.1", "@types/supertest": "^2.0.8", "mock-fs": "^5.1.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 2bbd544eea..52c1f3caa3 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/plugin-auth-backend +## 0.9.0 + +### Minor Changes + +- cef64b1561: **BREAKING** Added `tokenManager` as a required property for the auth-backend `createRouter` function. This dependency is used to issue server tokens that are used by the `CatalogIdentityClient` when looking up users and their group membership during authentication. + + These changes are **required** to `packages/backend/src/plugins/auth.ts`: + + ```diff + export default async function createPlugin({ + logger, + database, + config, + discovery, + + tokenManager, + }: PluginEnvironment): Promise { + return await createRouter({ + logger, + config, + database, + discovery, + + tokenManager, + }); + } + ``` + + **BREAKING** The `CatalogIdentityClient` constructor now expects a `TokenManager` instead of a `TokenIssuer`. The `TokenManager` interface is used to generate a server token when [resolving a user's identity and membership through the catalog](https://backstage.io/docs/auth/identity-resolver). Using server tokens for these requests allows the auth-backend to bypass authorization checks when permissions are enabled for Backstage. This change will break apps that rely on the user tokens that were previously used by the client. Refer to the ["Backend-to-backend Authentication" tutorial](https://backstage.io/docs/tutorials/backend-to-backend-auth) for more information on server token usage. + +### Patch Changes + +- 9d75a939b6: Fixed a bug where providers that tracked the granted scopes through a cookie would not take failed authentication attempts into account. +- 28a5f9d0b1: chore(deps): bump `passport` from 0.4.1 to 0.5.2 +- 5d09bdd1de: Added custom `callbackUrl` support for multiple providers. `v0.8.0` introduced this change for `github`, and now we're adding the same capability to the following providers: `atlassian, auth0, bitbucket, gitlab, google, microsoft, oauth2, oidc, okta, onelogin`. +- 648606b3ac: Added support for storing static GitHub access tokens in cookies and using them to refresh the Backstage session. +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.9.0-next.1 + +### Patch Changes + +- 9d75a939b6: Fixed a bug where providers that tracked the granted scopes through a cookie would not take failed authentication attempts into account. +- 648606b3ac: Added support for storing static GitHub access tokens in cookies and using them to refresh the Backstage session. +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + +## 0.9.0-next.0 + +### Minor Changes + +- cef64b1561: **BREAKING** Added `tokenManager` as a required property for the auth-backend `createRouter` function. This dependency is used to issue server tokens that are used by the `CatalogIdentityClient` when looking up users and their group membership during authentication. + + These changes are **required** to `packages/backend/src/plugins/auth.ts`: + + ```diff + export default async function createPlugin({ + logger, + database, + config, + discovery, + + tokenManager, + }: PluginEnvironment): Promise { + return await createRouter({ + logger, + config, + database, + discovery, + + tokenManager, + }); + } + ``` + + **BREAKING** The `CatalogIdentityClient` constructor now expects a `TokenManager` instead of a `TokenIssuer`. The `TokenManager` interface is used to generate a server token when [resolving a user's identity and membership through the catalog](https://backstage.io/docs/auth/identity-resolver). Using server tokens for these requests allows the auth-backend to bypass authorization checks when permissions are enabled for Backstage. This change will break apps that rely on the user tokens that were previously used by the client. Refer to the ["Backend-to-backend Authentication" tutorial](https://backstage.io/docs/tutorials/backend-to-backend-auth) for more information on server token usage. + +### Patch Changes + +- 28a5f9d0b1: chore(deps): bump `passport` from 0.4.1 to 0.5.2 + ## 0.8.0 ### Minor Changes diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 7cdc33f0e2..88b75d99b0 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -592,6 +592,7 @@ export type OAuthState = { nonce: string; env: string; origin?: string; + scope?: string; }; // @public @@ -734,6 +735,6 @@ export type WebMessageResponse = // // src/identity/types.d.ts:31:9 - (ae-forgotten-export) The symbol "AnyJWK" needs to be exported by the entry point index.d.ts // src/providers/aws-alb/provider.d.ts:77:5 - (ae-forgotten-export) The symbol "AwsAlbResult" needs to be exported by the entry point index.d.ts -// src/providers/github/provider.d.ts:81:5 - (ae-forgotten-export) The symbol "StateEncoder" needs to be exported by the entry point index.d.ts +// src/providers/github/provider.d.ts:97:5 - (ae-forgotten-export) The symbol "StateEncoder" needs to be exported by the entry point index.d.ts // src/providers/types.d.ts:98:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 5b4e4b22ba..e077444292 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend", "description": "A Backstage backend plugin that handles authentication", - "version": "0.8.0", + "version": "0.9.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", @@ -73,8 +73,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/test-utils": "^0.2.4", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index a85dd49fba..d1057b19a8 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -17,7 +17,7 @@ import express from 'express'; import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter'; import { encodeState } from './helpers'; -import { OAuthHandlers, OAuthResponse } from './types'; +import { OAuthHandlers, OAuthResponse, OAuthState } from './types'; const mockResponseData = { providerInfo: { @@ -148,6 +148,102 @@ describe('OAuthAdapter', () => { ); }); + it('persists scope through cookie if enabled', async () => { + const handlers = { + start: jest.fn(async (_req: { state: OAuthState }) => ({ + url: '/url', + status: 301, + })), + handler: jest.fn(async () => ({ response: mockResponseData })), + refresh: jest.fn(async () => ({ response: mockResponseData })), + }; + const oauthProvider = new OAuthAdapter(handlers, { + ...oAuthProviderOptions, + disableRefresh: false, + persistScopes: true, + }); + + // First we test the /start request, making sure state is set + const mockStartReq = { + query: { + scope: 'user', + env: 'development', + }, + } as unknown as express.Request; + const mockStartRes = { + cookie: jest.fn().mockReturnThis(), + end: jest.fn().mockReturnThis(), + setHeader: jest.fn().mockReturnThis(), + statusCode: jest.fn().mockReturnThis(), + } as unknown as express.Response; + + await oauthProvider.start(mockStartReq, mockStartRes); + + expect(handlers.start).toHaveBeenCalledTimes(1); + expect(handlers.start).toHaveBeenCalledWith({ + query: { + scope: 'user', + env: 'development', + }, + scope: 'user', + state: { + nonce: expect.any(String), + env: 'development', + origin: undefined, + scope: 'user', + }, + }); + + // Then test the /handler, making sure the granted scope cookie is set + const providedState = handlers.start.mock.calls[0][0].state; + const mockHandleReq = { + cookies: { + 'test-provider-nonce': providedState.nonce, + }, + query: { + state: encodeState(providedState), + }, + } as unknown as express.Request; + const mockHandleRes = { + cookie: jest.fn().mockReturnThis(), + setHeader: jest.fn().mockReturnThis(), + end: jest.fn().mockReturnThis(), + } as unknown as express.Response; + + await oauthProvider.frameHandler(mockHandleReq, mockHandleRes); + expect(mockHandleRes.cookie).toHaveBeenCalledTimes(1); + expect(mockHandleRes.cookie).toHaveBeenCalledWith( + 'test-provider-granted-scope', + 'user', + expect.objectContaining({ + path: '/auth/test-provider', + maxAge: THOUSAND_DAYS_MS, + }), + ); + + // Them make sure scopes are forwarded correctly during refresh + const mockRefreshReq = { + query: { scope: 'ignore-me' }, + cookies: { + 'test-provider-granted-scope': 'user', + 'test-provider-refresh-token': 'refresh-token', + }, + header: jest.fn().mockReturnValue('XMLHttpRequest'), + } as unknown as express.Request; + const mockRefreshRes = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + } as unknown as express.Response; + await oauthProvider.refresh(mockRefreshReq, mockRefreshRes); + expect(handlers.refresh).toHaveBeenCalledTimes(1); + expect(handlers.refresh).toHaveBeenCalledWith( + expect.objectContaining({ + scope: 'user', + refreshToken: 'refresh-token', + }), + ); + }); + it('does not set the refresh cookie if refresh is disabled', async () => { const oauthProvider = new OAuthAdapter(providerInstance, { ...oAuthProviderOptions, diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 564d5c20de..2e2d26db61 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import express from 'express'; +import express, { CookieOptions } from 'express'; import crypto from 'crypto'; import { URL } from 'url'; import { @@ -90,10 +90,20 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { }); } + private readonly baseCookieOptions: CookieOptions; + constructor( private readonly handlers: OAuthHandlers, private readonly options: Options, - ) {} + ) { + this.baseCookieOptions = { + httpOnly: true, + sameSite: 'lax', + secure: this.options.secure, + path: this.options.cookiePath, + domain: this.options.cookieDomain, + }; + } async start(req: express.Request, res: express.Response): Promise { // retrieve scopes from request @@ -105,15 +115,17 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { throw new InputError('No env provided in request query parameters'); } - if (this.options.persistScopes) { - this.setScopesCookie(res, scope); - } - const nonce = crypto.randomBytes(16).toString('base64'); // set a nonce cookie before redirecting to oauth provider this.setNonceCookie(res, nonce); - const state = { nonce, env, origin }; + const state: OAuthState = { nonce, env, origin }; + + // If scopes are persisted then we pass them through the state so that we + // can set the cookie on successful auth + if (this.options.persistScopes) { + state.scope = scope; + } const forwardReq = Object.assign(req, { scope, state }); const { url, status } = await this.handlers.start( @@ -151,12 +163,11 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { const { response, refreshToken } = await this.handlers.handler(req); - if (this.options.persistScopes) { - const grantedScopes = this.getScopesFromCookie( - req, - this.options.providerId, - ); - response.providerInfo.scope = grantedScopes; + // Store the scope that we have been granted for this session. This is useful if + // the provider does not return granted scopes on refresh or if they are normalized. + if (this.options.persistScopes && state.scope) { + this.setGrantedScopeCookie(res, state.scope); + response.providerInfo.scope = state.scope; } if (refreshToken && !this.options.disableRefresh) { @@ -214,8 +225,10 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { throw new InputError('Missing session cookie'); } - const scope = req.query.scope?.toString() ?? ''; - + let scope = req.query.scope?.toString() ?? ''; + if (this.options.persistScopes) { + scope = this.getGrantedScopeFromCookie(req); + } const forwardReq = Object.assign(req, { scope, refreshToken }); // get new access_token @@ -267,27 +280,20 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { private setNonceCookie = (res: express.Response, nonce: string) => { res.cookie(`${this.options.providerId}-nonce`, nonce, { maxAge: TEN_MINUTES_MS, - secure: this.options.secure, - sameSite: 'lax', - domain: this.options.cookieDomain, + ...this.baseCookieOptions, path: `${this.options.cookiePath}/handler`, - httpOnly: true, }); }; - private setScopesCookie = (res: express.Response, scope: string) => { - res.cookie(`${this.options.providerId}-scope`, scope, { - maxAge: TEN_MINUTES_MS, - secure: this.options.secure, - sameSite: 'lax', - domain: this.options.cookieDomain, - path: `${this.options.cookiePath}/handler`, - httpOnly: true, + private setGrantedScopeCookie = (res: express.Response, scope: string) => { + res.cookie(`${this.options.providerId}-granted-scope`, scope, { + maxAge: THOUSAND_DAYS_MS, + ...this.baseCookieOptions, }); }; - private getScopesFromCookie = (req: express.Request, providerId: string) => { - return req.cookies[`${providerId}-scope`]; + private getGrantedScopeFromCookie = (req: express.Request) => { + return req.cookies[`${this.options.providerId}-granted-scope`]; }; private setRefreshTokenCookie = ( @@ -296,22 +302,14 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { ) => { res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, { maxAge: THOUSAND_DAYS_MS, - secure: this.options.secure, - sameSite: 'lax', - domain: this.options.cookieDomain, - path: this.options.cookiePath, - httpOnly: true, + ...this.baseCookieOptions, }); }; private removeRefreshTokenCookie = (res: express.Response) => { res.cookie(`${this.options.providerId}-refresh-token`, '', { maxAge: 0, - secure: this.options.secure, - sameSite: 'lax', - domain: this.options.cookieDomain, - path: this.options.cookiePath, - httpOnly: true, + ...this.baseCookieOptions, }); }; } diff --git a/plugins/auth-backend/src/lib/oauth/types.ts b/plugins/auth-backend/src/lib/oauth/types.ts index 9ddef007a7..6973e99569 100644 --- a/plugins/auth-backend/src/lib/oauth/types.ts +++ b/plugins/auth-backend/src/lib/oauth/types.ts @@ -87,6 +87,7 @@ export type OAuthState = { nonce: string; env: string; origin?: string; + scope?: string; }; export type OAuthStartRequest = express.Request<{}> & { diff --git a/plugins/auth-backend/src/providers/atlassian/provider.ts b/plugins/auth-backend/src/providers/atlassian/provider.ts index 2696fd233f..79bcb5c909 100644 --- a/plugins/auth-backend/src/providers/atlassian/provider.ts +++ b/plugins/auth-backend/src/providers/atlassian/provider.ts @@ -205,7 +205,10 @@ export const createAtlassianProvider = ( const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const scopes = envConfig.getString('scopes'); - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, @@ -231,6 +234,7 @@ export const createAtlassianProvider = ( disableRefresh: true, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index 15578117ea..fbd57c0b76 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -228,7 +228,10 @@ export const createAuth0Provider = ( const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const domain = envConfig.getString('domain'); - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, @@ -259,6 +262,7 @@ export const createAuth0Provider = ( disableRefresh: true, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/auth-backend/src/providers/bitbucket/provider.ts b/plugins/auth-backend/src/providers/bitbucket/provider.ts index 4d5bdcf6f6..1cc0e60bd8 100644 --- a/plugins/auth-backend/src/providers/bitbucket/provider.ts +++ b/plugins/auth-backend/src/providers/bitbucket/provider.ts @@ -280,7 +280,10 @@ export const createBitbucketProvider = ( OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, @@ -309,6 +312,7 @@ export const createBitbucketProvider = ( disableRefresh: false, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/auth-backend/src/providers/github/provider.test.ts b/plugins/auth-backend/src/providers/github/provider.test.ts index 15eeccc07c..206df52083 100644 --- a/plugins/auth-backend/src/providers/github/provider.test.ts +++ b/plugins/auth-backend/src/providers/github/provider.test.ts @@ -98,6 +98,7 @@ describe('GithubAuthProvider', () => { providerInfo: { accessToken: '19xasczxcm9n7gacn9jdgm19me', scope: 'read:scope', + expiresInSeconds: 3600, }, profile: { email: 'jimmymarkum@gmail.com', @@ -143,6 +144,7 @@ describe('GithubAuthProvider', () => { providerInfo: { accessToken: '19xasczxcm9n7gacn9jdgm19me', scope: 'read:scope', + expiresInSeconds: 3600, }, profile: { displayName: 'Jimmy Markum', @@ -186,6 +188,7 @@ describe('GithubAuthProvider', () => { providerInfo: { accessToken: '19xasczxcm9n7gacn9jdgm19me', scope: 'read:scope', + expiresInSeconds: 3600, }, profile: { displayName: 'jimmymarkum', @@ -230,6 +233,7 @@ describe('GithubAuthProvider', () => { accessToken: 'ajakljsdoiahoawxbrouawucmbawe.awkxjemaneasdxwe.sodijxqeqwexeqwxe', scope: 'read:user', + expiresInSeconds: 3600, }, profile: { displayName: 'Dave Boyle', @@ -316,7 +320,7 @@ describe('GithubAuthProvider', () => { ], }); - const result = await provider.refresh({} as any); + const result = await provider.refresh({ scope: 'actual-scope' } as any); expect(result).toEqual({ response: { @@ -332,11 +336,65 @@ describe('GithubAuthProvider', () => { providerInfo: { accessToken: 'a.b.c', expiresInSeconds: 123, - scope: 'read_user', + scope: 'actual-scope', }, }, refreshToken: 'dont-forget-to-send-refresh', }); + + mockRefreshToken.mockRestore(); + mockUserProfile.mockRestore(); + }); + + it('should use access token as refresh token', async () => { + const mockUserProfile = jest.spyOn( + helpers, + 'executeFetchUserProfileStrategy', + ) as unknown as jest.MockedFunction<() => Promise>; + + mockUserProfile.mockResolvedValueOnce({ + id: 'mockid', + username: 'mockuser', + provider: 'github', + displayName: 'Mocked User', + emails: [ + { + value: 'mockuser@gmail.com', + }, + ], + }); + + const result = await provider.refresh({ + refreshToken: 'access-token.le-token', + scope: 'the-scope', + } as any); + + expect(mockUserProfile).toHaveBeenCalledTimes(1); + expect(mockUserProfile).toHaveBeenCalledWith( + expect.anything(), + 'le-token', + ); + expect(result).toEqual({ + response: { + backstageIdentity: { + id: 'mockuser', + token: 'token-for-user:default/mockuser', + }, + profile: { + displayName: 'Mocked User', + email: 'mockuser@gmail.com', + picture: undefined, + }, + providerInfo: { + accessToken: 'le-token', + expiresInSeconds: 3600, + scope: 'the-scope', + }, + }, + refreshToken: 'access-token.le-token', + }); + + mockUserProfile.mockRestore(); }); }); }); diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index 9dc70cf60e..ec7f8cafa6 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -41,11 +41,15 @@ import { OAuthStartRequest, encodeState, OAuthRefreshRequest, - OAuthResponse, } from '../../lib/oauth'; import { CatalogIdentityClient } from '../../lib/catalog'; import { TokenIssuer } from '../../identity'; +const ACCESS_TOKEN_PREFIX = 'access-token.'; + +// TODO(Rugvip): Auth providers need a way to access this in a less hardcoded way +const BACKSTAGE_SESSION_EXPIRATION = 3600; + type PrivateInfo = { refreshToken?: string; }; @@ -123,31 +127,69 @@ export class GithubAuthProvider implements OAuthHandlers { PrivateInfo >(req, this._strategy); + let refreshToken = privateInfo.refreshToken; + + // If we do not have a real refresh token and we have a non-expiring + // access token, then we use that as our refresh token. + if (!refreshToken && !result.params.expires_in) { + refreshToken = ACCESS_TOKEN_PREFIX + result.accessToken; + } + return { response: await this.handleResult(result), - refreshToken: privateInfo.refreshToken, + refreshToken, }; } async refresh(req: OAuthRefreshRequest) { - const { accessToken, refreshToken, params } = - await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); - const fullProfile = await executeFetchUserProfileStrategy( - this._strategy, - accessToken, - ); + // We've enable persisting scope in the OAuth provider, so scope here will + // be whatever was stored in the cookie + const { scope, refreshToken } = req; + // This is the OAuth App flow. A non-expiring access token is stored in the + // refresh token cookie. We use that token to fetch the user profile and + // refresh the Backstage session when needed. + if (refreshToken?.startsWith(ACCESS_TOKEN_PREFIX)) { + const accessToken = refreshToken.slice(ACCESS_TOKEN_PREFIX.length); + + const fullProfile = await executeFetchUserProfileStrategy( + this._strategy, + accessToken, + ).catch(error => { + if (error.oauthError?.statusCode === 401) { + throw new Error('Invalid access token'); + } + throw error; + }); + + return { + response: await this.handleResult({ + fullProfile, + params: { scope }, + accessToken, + }), + refreshToken, + }; + } + + // This is the App flow, which is close to a standard OAuth refresh flow. It has a + // pretty long session expiration, and it also ignores the requested scope, instead + // just allowing access to whatever is configured as part of the app installation. + const result = await executeRefreshTokenStrategy( + this._strategy, + refreshToken, + scope, + ); return { response: await this.handleResult({ - fullProfile, - params, - accessToken, + fullProfile: await executeFetchUserProfileStrategy( + this._strategy, + result.accessToken, + ), + params: { ...result.params, scope }, + accessToken: result.accessToken, }), - refreshToken, + refreshToken: result.refreshToken, }; } @@ -160,27 +202,41 @@ export class GithubAuthProvider implements OAuthHandlers { const { profile } = await this.authHandler(result, context); const expiresInStr = result.params.expires_in; - const response: OAuthResponse = { - providerInfo: { - accessToken: result.accessToken, - scope: result.params.scope, - expiresInSeconds: - expiresInStr === undefined ? undefined : Number(expiresInStr), - }, - profile, - }; + let expiresInSeconds = + expiresInStr === undefined ? undefined : Number(expiresInStr); + + let backstageIdentity = undefined; if (this.signInResolver) { - response.backstageIdentity = await this.signInResolver( + backstageIdentity = await this.signInResolver( { result, profile, }, context, ); + + // GitHub sessions last longer than Backstage sessions, so if we're using + // GitHub for sign-in, then we need to expire the sessions earlier + if (expiresInSeconds) { + expiresInSeconds = Math.min( + expiresInSeconds, + BACKSTAGE_SESSION_EXPIRATION, + ); + } else { + expiresInSeconds = BACKSTAGE_SESSION_EXPIRATION; + } } - return response; + return { + backstageIdentity, + providerInfo: { + accessToken: result.accessToken, + scope: result.params.scope, + expiresInSeconds, + }, + profile, + }; } } diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index da816f2fd2..6689aba884 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -236,7 +236,10 @@ export const createGitlabProvider = ( const clientSecret = envConfig.getString('clientSecret'); const audience = envConfig.getOptionalString('audience'); const baseUrl = audience || 'https://gitlab.com'; - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, @@ -272,6 +275,7 @@ export const createGitlabProvider = ( disableRefresh: false, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 13c5093aa5..fbaf8c7462 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -266,7 +266,10 @@ export const createGoogleProvider = ( OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, @@ -304,6 +307,7 @@ export const createGoogleProvider = ( disableRefresh: false, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index 9249f643dc..25c940547b 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -276,7 +276,10 @@ export const createMicrosoftProvider = ( const clientSecret = envConfig.getString('clientSecret'); const tenantId = envConfig.getString('tenantId'); - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`; const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; @@ -318,6 +321,7 @@ export const createMicrosoftProvider = ( disableRefresh: false, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index 8e69a3ee1d..0906005791 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -240,7 +240,10 @@ export const createOAuth2Provider = ( OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; const authorizationUrl = envConfig.getString('authorizationUrl'); const tokenUrl = envConfig.getString('tokenUrl'); const scope = envConfig.getOptionalString('scope'); @@ -288,6 +291,7 @@ export const createOAuth2Provider = ( disableRefresh, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index fa46294784..e77812e750 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -264,7 +264,10 @@ export const createOidcProvider = ( OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; const metadataUrl = envConfig.getString('metadataUrl'); const tokenSignedResponseAlg = envConfig.getOptionalString( 'tokenSignedResponseAlg', @@ -313,6 +316,7 @@ export const createOidcProvider = ( disableRefresh: false, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index def0da694e..0ba7d241d3 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -276,7 +276,10 @@ export const createOktaProvider = ( const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const audience = envConfig.getString('audience'); - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; // This is a safe assumption as `passport-okta-oauth` uses the audience // as the base for building the authorization, token, and user info URLs. @@ -322,6 +325,7 @@ export const createOktaProvider = ( disableRefresh: false, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index a5ab4f658b..e14e8548a0 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -227,7 +227,10 @@ export const createOneLoginProvider = ( const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const issuer = envConfig.getString('issuer'); - const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); + const callbackUrl = + customCallbackUrl || + `${globalConfig.baseUrl}/${providerId}/handler/frame`; const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, @@ -258,6 +261,7 @@ export const createOneLoginProvider = ( disableRefresh: false, providerId, tokenIssuer, + callbackUrl, }); }); }; diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index 16b4a0d182..e2aad374b3 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-azure-devops-backend +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + ## 0.3.1 ### Patch Changes diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index d0bcca2db9..4125e9be24 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-backend", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", "@backstage/plugin-azure-devops-common": "^0.2.0", "@types/express": "^4.17.6", @@ -32,7 +32,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", "msw": "^0.35.0" diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json index 0b9b3ad3d6..8a3c884fed 100644 --- a/plugins/azure-devops-common/package.json +++ b/plugins/azure-devops-common/package.json @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "devDependencies": { - "@backstage/cli": "^0.13.0" + "@backstage/cli": "^0.13.1-next.0" }, "files": [ "dist" diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 4076ca6fc0..8e300e9699 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-azure-devops +## 0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.1.12 ### Patch Changes diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index bc92e208d3..7052dee6d5 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops", - "version": "0.1.12", + "version": "0.1.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,11 +28,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", "@backstage/plugin-azure-devops-common": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -46,10 +46,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index 4f54700697..54c46a6f3e 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-badges-backend +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + ## 0.1.16 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index a2c74623f1..f5d533522e 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges-backend", "description": "A Backstage backend plugin that generates README badges for your entities", - "version": "0.1.16", + "version": "0.1.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", @@ -45,7 +45,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index 32226f40db..e49dc620ee 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-badges +## 0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.2.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.2.20 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 3a21138d70..c27375b995 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges", "description": "A Backstage plugin that generates README badges for your entities", - "version": "0.2.20", + "version": "0.2.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,10 +28,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -43,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md index ab4bd8d674..4262b3fe11 100644 --- a/plugins/bazaar-backend/CHANGELOG.md +++ b/plugins/bazaar-backend/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-bazaar-backend +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + - @backstage/backend-test-utils@0.1.16 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + - @backstage/backend-test-utils@0.1.16-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.1.16-next.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 14f2332eb2..a713e61503 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar-backend", - "version": "0.1.7", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", - "@backstage/backend-test-utils": "^0.1.15", + "@backstage/backend-common": "^0.10.6", + "@backstage/backend-test-utils": "^0.1.16", "@backstage/config": "^0.1.13", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -31,7 +31,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0" + "@backstage/cli": "^0.13.1" }, "files": [ "dist", diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index ea92aa9f27..8fd907c6bd 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-bazaar +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/cli@0.13.1 + - @backstage/plugin-catalog@0.7.11 + +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/cli@0.13.1-next.0 + - @backstage/plugin-catalog@0.7.11-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.1.11 ### Patch Changes diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index 40ea514392..111bccced6 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.11", + "version": "0.1.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,11 +23,11 @@ "dependencies": { "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", - "@backstage/cli": "^0.13.0", - "@backstage/core-components": "^0.8.6", + "@backstage/cli": "^0.13.1", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog": "^0.7.10", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog": "^0.7.11", + "@backstage/plugin-catalog-react": "^0.6.13", "@date-io/luxon": "2.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -44,8 +44,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/dev-utils": "^0.2.19", + "@backstage/cli": "^0.13.1", + "@backstage/dev-utils": "^0.2.20", "@testing-library/jest-dom": "^5.10.1", "cross-fetch": "^3.0.6" }, diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index a62137d1e1..e37e17882c 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-bitrise +## 0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.1.23 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 87f377632b..6c8dea09b4 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitrise", "description": "A Backstage plugin that integrates towards Bitrise", - "version": "0.1.23", + "version": "0.1.24", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,9 +22,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -40,10 +40,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 4495964ca8..67e8aeade2 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.21.2 + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.21.2-next.0 + ## 0.3.10 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 1b43e66b47..cb57a8fbed 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", "description": "A Backstage catalog backend modules that helps integrate towards LDAP", - "version": "0.3.10", + "version": "0.3.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-backend": "^0.21.1", + "@backstage/plugin-catalog-backend": "^0.21.2", "@backstage/types": "^0.1.1", "@types/ldapjs": "^2.2.0", "ldapjs": "^2.2.0", @@ -40,7 +40,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index bd04fbcef7..6d8771b609 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.2.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.21.2 + +## 0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.21.2-next.0 + ## 0.2.13 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 09d55c42e9..795a113144 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", "description": "A Backstage catalog backend modules that helps integrate towards Microsoft Graph", - "version": "0.2.13", + "version": "0.2.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ "@azure/msal-node": "^1.1.0", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", - "@backstage/plugin-catalog-backend": "^0.21.1", + "@backstage/plugin-catalog-backend": "^0.21.2", "@microsoft/microsoft-graph-types": "^2.6.0", "@types/node-fetch": "^2.5.12", "lodash": "^4.17.21", @@ -42,9 +42,9 @@ "qs": "^6.9.4" }, "devDependencies": { - "@backstage/backend-common": "^0.10.5", - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/backend-common": "^0.10.6", + "@backstage/cli": "^0.13.1", + "@backstage/test-utils": "^0.2.4", "@types/lodash": "^4.14.151", "msw": "^0.35.0" }, diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 523fcd0bd1..e46d30350f 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-catalog-backend +## 0.21.2 + +### Patch Changes + +- fac5f112b4: chore(deps): bump `prom-client` from 13.2.0 to 14.0.1 +- 5bbffa60be: Pass authorization token to location service inside location api routes +- Updated dependencies + - @backstage/plugin-catalog-common@0.1.2 + - @backstage/backend-common@0.10.6 + - @backstage/plugin-permission-node@0.4.2 + +## 0.21.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@0.1.2-next.0 + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-permission-node@0.4.2-next.1 + +## 0.21.2-next.0 + +### Patch Changes + +- fac5f112b4: chore(deps): bump `prom-client` from 13.2.0 to 14.0.1 +- 5bbffa60be: Pass authorization token to location service inside location api routes +- Updated dependencies + - @backstage/plugin-permission-node@0.4.2-next.0 + ## 0.21.1 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 748ff77592..f2f9d91b1b 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "0.21.1", + "version": "0.21.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,15 +30,15 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", - "@backstage/plugin-catalog-common": "^0.1.1", + "@backstage/plugin-catalog-common": "^0.1.2", "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/plugin-permission-node": "^0.4.1", + "@backstage/plugin-permission-node": "^0.4.2", "@backstage/search-common": "^0.2.2", "@backstage/types": "^0.1.1", "@octokit/graphql": "^4.5.8", @@ -65,10 +65,10 @@ "yup": "^0.32.9" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.15", - "@backstage/cli": "^0.13.0", + "@backstage/backend-test-utils": "^0.1.16", + "@backstage/cli": "^0.13.1", "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/test-utils": "^0.2.4", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 77bc30964c..a236e55857 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-common +## 0.1.2 + +### Patch Changes + +- ba59832aed: Adds new `catalogEntityCreatePermission` which can be imported and used when authoring a permission policy to restrict/grant a user's access to the catalog import plugin. (And the "Register Existing Component" button which navigates there). + +## 0.1.2-next.0 + +### Patch Changes + +- ba59832aed: Adds new `catalogEntityCreatePermission` which can be imported and used when authoring a permission policy to restrict/grant a user's access to the catalog import plugin. (And the "Register Existing Component" button which navigates there). + ## 0.1.1 ### Patch Changes diff --git a/plugins/catalog-common/api-report.md b/plugins/catalog-common/api-report.md index e2ee2d3225..9826a8ddef 100644 --- a/plugins/catalog-common/api-report.md +++ b/plugins/catalog-common/api-report.md @@ -5,6 +5,9 @@ ```ts import { Permission } from '@backstage/plugin-permission-common'; +// @public +export const catalogEntityCreatePermission: Permission; + // @public export const catalogEntityDeletePermission: Permission; diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 05fc678e59..b9cf9a20dd 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-common", "description": "Common functionalities for the catalog plugin", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "@backstage/plugin-permission-common": "^0.4.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0" + "@backstage/cli": "^0.13.1" }, "files": [ "dist" diff --git a/plugins/catalog-common/src/index.ts b/plugins/catalog-common/src/index.ts index 05d5ed2ba5..9eddf37452 100644 --- a/plugins/catalog-common/src/index.ts +++ b/plugins/catalog-common/src/index.ts @@ -24,6 +24,7 @@ export { RESOURCE_TYPE_CATALOG_ENTITY, catalogEntityReadPermission, + catalogEntityCreatePermission, catalogEntityDeletePermission, catalogEntityRefreshPermission, catalogLocationReadPermission, diff --git a/plugins/catalog-common/src/permissions.ts b/plugins/catalog-common/src/permissions.ts index b4c6c4377c..52c225e441 100644 --- a/plugins/catalog-common/src/permissions.ts +++ b/plugins/catalog-common/src/permissions.ts @@ -38,6 +38,20 @@ export const catalogEntityReadPermission: Permission = { resourceType: RESOURCE_TYPE_CATALOG_ENTITY, }; +/** + * This permission is used to authorize actions that involve creating a new + * catalog entity. This includes registering an existing component into the + * catalog. + * @public + */ +export const catalogEntityCreatePermission: Permission = { + name: 'catalog.entity.create', + attributes: { + action: 'create', + }, + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, +}; + /** * This permission is used to designate actions that involve removing one or * more entities from the catalog. diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index b4f2fdac78..56446575da 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-graph +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.2.8 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 9b61fd3438..10dafe8daa 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.2.8", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,9 +23,9 @@ "dependencies": { "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 4c10c93354..affc3e0ee6 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -43,8 +43,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1-next.1", + "@backstage/test-utils": "^0.2.4-next.0", "@graphql-codegen/cli": "^2.3.1", "@graphql-codegen/typescript": "^2.4.2", "@graphql-codegen/typescript-resolvers": "^2.4.3", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 81fb253736..f3aae8182e 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-catalog-import +## 0.8.0 + +### Minor Changes + +- 2e8764b95f: Make filename, branch name and examples URLs used in catalog import customizable. + + Catalog backend ingestion loop can be already configured to fetch targets with custom catalog filename (other than `catalog-info.yaml`). It's now possible to customize said filename and branch name used in pull requests created by catalog import flow too. This allows organizations to further customize Backstage experience and to better reflect their branding. + + Filename (default: `catalog-info.yaml`) and branch name (default: `backstage-integration`) used in pull requests can be configured in `app-config.yaml` as follows: + + ```yaml + // app-config.yaml + + catalog: + import: + entityFilename: anvil.yaml + pullRequestBranchName: anvil-integration + ``` + + Following React components have also been updated to accept optional props for providing example entity and repository paths: + + ```tsx + + ``` + + ```tsx + + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/integration-react@0.1.20 + +## 0.8.0-next.0 + +### Minor Changes + +- 2e8764b95f: Make filename, branch name and examples URLs used in catalog import customizable. + + Catalog backend ingestion loop can be already configured to fetch targets with custom catalog filename (other than `catalog-info.yaml`). It's now possible to customize said filename and branch name used in pull requests created by catalog import flow too. This allows organizations to further customize Backstage experience and to better reflect their branding. + + Filename (default: `catalog-info.yaml`) and branch name (default: `backstage-integration`) used in pull requests can be configured in `app-config.yaml` as follows: + + ```yaml + // app-config.yaml + + catalog: + import: + entityFilename: anvil.yaml + pullRequestBranchName: anvil-integration + ``` + + Following React components have also been updated to accept optional props for providing example entity and repository paths: + + ```tsx + + ``` + + ```tsx + + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/integration-react@0.1.20-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.7.10 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 2508543541..0b452aa239 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-import", "description": "A Backstage plugin the helps you import entities into your catalog", - "version": "0.7.10", + "version": "0.8.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,13 +33,13 @@ "dependencies": { "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/config": "^0.1.13", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", - "@backstage/integration-react": "^0.1.19", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/integration-react": "^0.1.20", + "@backstage/plugin-catalog-react": "^0.6.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -57,10 +57,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index a98178af1d..e38f17b37d 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-catalog-react +## 0.6.13 + +### Patch Changes + +- f7257dff6f: The `` component now accepts a `noTrack` prop, which prevents the `click` event from being captured by the Analytics API. This can be used if tracking is explicitly not warranted, or in order to use custom link tracking in specific situations. +- 300f8cdaee: Fix bug: previously the filter would be set to "all" on page load, even if the + `initiallySelectedFilter` on the `DefaultCatalogPage` was set to something else, + or a different query parameter was supplied. Now, the prop and query parameters + control the filter as expected. Additionally, after this change any filters + which match 0 items will be disabled, and the filter will be reverted to 'all' + if they're set on page load. +- 6acc8f7db7: Add caching to the useEntityPermission hook + + The hook now caches the authorization decision based on the permission + the entity, and returns the cache match value as the default `allowed` value while loading. This helps avoid flicker in UI elements that would be conditionally rendered based on the `allowed` result of this hook. + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.6.13-next.1 + +### Patch Changes + +- f7257dff6f: The `` component now accepts a `noTrack` prop, which prevents the `click` event from being captured by the Analytics API. This can be used if tracking is explicitly not warranted, or in order to use custom link tracking in specific situations. +- 300f8cdaee: Fix bug: previously the filter would be set to "all" on page load, even if the + `initiallySelectedFilter` on the `DefaultCatalogPage` was set to something else, + or a different query parameter was supplied. Now, the prop and query parameters + control the filter as expected. Additionally, after this change any filters + which match 0 items will be disabled, and the filter will be reverted to 'all' + if they're set on page load. +- 6acc8f7db7: Add caching to the useEntityPermission hook + + The hook now caches the authorization decision based on the permission + the entity, and returns the cache match value as the default `allowed` value while loading. This helps avoid flicker in UI elements that would be conditionally rendered based on the `allowed` result of this hook. + +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + +## 0.6.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.6.12 ### Patch Changes diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 398bace903..8c3ba0daa3 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -573,6 +573,7 @@ export const EntityRefLink: React_2.ForwardRefExoticComponent< | 'gutterBottom' | 'paragraph' | 'variantMapping' + | 'noTrack' | 'TypographyClasses' | 'entityRef' | 'defaultKind' diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 66045c1ff1..9018ea75d7 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "0.6.12", + "version": "0.6.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "dependencies": { "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", @@ -54,10 +54,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/plugin-catalog-common": "^0.1.1", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/plugin-catalog-common": "^0.1.2", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx index f6a4c59374..b776f26926 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx @@ -69,11 +69,13 @@ const apis = TestApiRegistry.from( [storageApiRef, MockStorageApi.create()], ); -const mockIsOwnedEntity = (entity: Entity) => - entity.metadata.name === 'component-1'; +const mockIsOwnedEntity = jest.fn( + (entity: Entity) => entity.metadata.name === 'component-1', +); -const mockIsStarredEntity = (entity: Entity) => - entity.metadata.name === 'component-3'; +const mockIsStarredEntity = jest.fn( + (entity: Entity) => entity.metadata.name === 'component-3', +); jest.mock('../../hooks', () => { const actual = jest.requireActual('../../hooks'); @@ -250,4 +252,86 @@ describe('', () => { ), }); }); + + describe.each` + type | filterFn + ${'owned'} | ${mockIsOwnedEntity} + ${'starred'} | ${mockIsStarredEntity} + `('filter resetting for $type entities', ({ type, filterFn }) => { + let updateFilters: jest.Mock; + + const picker = ({ loading }: { loading: boolean }) => ( + + + + + + ); + + beforeEach(() => { + updateFilters = jest.fn(); + }); + + describe(`when there are no ${type} entities match the filter`, () => { + beforeEach(() => { + filterFn.mockReturnValue(false); + }); + + it('does not reset the filter while entities are loading', () => { + render(picker({ loading: true })); + + expect(updateFilters).not.toHaveBeenCalledWith({ + user: new UserListFilter( + 'all', + mockIsOwnedEntity, + mockIsStarredEntity, + ), + }); + }); + + it('resets the filter to "all" when entities are loaded', () => { + render(picker({ loading: false })); + + expect(updateFilters).toHaveBeenLastCalledWith({ + user: new UserListFilter( + 'all', + mockIsOwnedEntity, + mockIsStarredEntity, + ), + }); + }); + }); + + describe(`when there are some ${type} entities present`, () => { + beforeEach(() => { + filterFn.mockReturnValue(true); + }); + + it('does not reset the filter while entities are loading', () => { + render(picker({ loading: true })); + + expect(updateFilters).not.toHaveBeenCalledWith({ + user: new UserListFilter( + 'all', + mockIsOwnedEntity, + mockIsStarredEntity, + ), + }); + }); + + it('does not reset the filter when entities are loaded', () => { + render(picker({ loading: false })); + + expect(updateFilters).toHaveBeenLastCalledWith({ + user: new UserListFilter( + type, + mockIsOwnedEntity, + mockIsStarredEntity, + ), + }); + }); + }); + }); }); diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index 8746752b08..d731a185fc 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -130,7 +130,7 @@ export const UserListPicker = ({ const classes = useStyles(); const configApi = useApi(configApiRef); const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; - const { filters, updateFilters, backendEntities, queryParameters } = + const { filters, updateFilters, backendEntities, queryParameters, loading } = useEntityListProvider(); // Remove group items that aren't in availableFilters and exclude @@ -161,19 +161,46 @@ export const UserListPicker = ({ [isOwnedEntity, isStarredEntity], ); + const [selectedUserFilter, setSelectedUserFilter] = useState( + [queryParameters.user].flat()[0] ?? initialFilter, + ); + // To show proper counts for each section, apply all other frontend filters _except_ the user // filter that's controlled by this picker. - const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] = - useState(backendEntities); - const totalOwnedUserEntities = entitiesWithoutUserFilter.filter(entity => - ownedFilter.filterEntity(entity), - ).length; - const [selectedUserFilter, setSelectedUserFilter] = useState( - totalOwnedUserEntities > 0 - ? [queryParameters.user].flat()[0] ?? initialFilter - : 'all', + const entitiesWithoutUserFilter = useMemo( + () => + backendEntities.filter( + reduceEntityFilters( + compact(Object.values({ ...filters, user: undefined })), + ), + ), + [filters, backendEntities], ); + const filterCounts = useMemo>( + () => ({ + all: entitiesWithoutUserFilter.length, + starred: entitiesWithoutUserFilter.filter(entity => + starredFilter.filterEntity(entity), + ).length, + owned: entitiesWithoutUserFilter.filter(entity => + ownedFilter.filterEntity(entity), + ).length, + }), + [entitiesWithoutUserFilter, starredFilter, ownedFilter], + ); + + useEffect(() => { + if ( + !loading && + !!selectedUserFilter && + selectedUserFilter !== 'all' && + filterCounts[selectedUserFilter] === 0 + ) { + setSelectedUserFilter('all'); + } + }, [loading, filterCounts, selectedUserFilter, setSelectedUserFilter]); + useEffect(() => { updateFilters({ user: selectedUserFilter @@ -186,26 +213,6 @@ export const UserListPicker = ({ }); }, [selectedUserFilter, isOwnedEntity, isStarredEntity, updateFilters]); - useEffect(() => { - const filterFn = reduceEntityFilters( - compact(Object.values({ ...filters, user: undefined })), - ); - setEntitiesWithoutUserFilter(backendEntities.filter(filterFn)); - }, [filters, backendEntities]); - - function getFilterCount(id: UserListFilterKind) { - switch (id) { - case 'owned': - return totalOwnedUserEntities; - case 'starred': - return entitiesWithoutUserFilter.filter(entity => - starredFilter.filterEntity(entity), - ).length; - default: - return entitiesWithoutUserFilter.length; - } - } - return ( {filterGroups.map(group => ( @@ -223,6 +230,8 @@ export const UserListPicker = ({ onClick={() => setSelectedUserFilter(item.id)} selected={item.id === filters.user?.value} className={classes.menuItem} + disabled={filterCounts[item.id] === 0} + data-testid={`user-picker-${item.id}`} > {item.icon && ( @@ -230,15 +239,10 @@ export const UserListPicker = ({ )} - - {item.label} - + {item.label} - {getFilterCount(item.id) ?? '-'} + {filterCounts[item.id] ?? '-'} ))} diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index bcbfdf4fc2..8ccca07609 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-catalog +## 0.7.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/plugin-catalog-common@0.1.2 + - @backstage/integration-react@0.1.20 + +## 0.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/plugin-catalog-react@0.6.13-next.1 + - @backstage/plugin-catalog-common@0.1.2-next.0 + +## 0.7.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/integration-react@0.1.20-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.7.10 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 066b2d99f5..0a3211fcaf 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "0.7.10", + "version": "0.7.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "dependencies": { "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", - "@backstage/integration-react": "^0.1.19", - "@backstage/plugin-catalog-common": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/integration-react": "^0.1.20", + "@backstage/plugin-catalog-common": "^0.1.2", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -54,11 +54,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", "@backstage/plugin-permission-react": "^0.3.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx index 135a2a590d..20acff3c50 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx @@ -263,10 +263,12 @@ describe('DefaultCatalogPage', () => { const { getByTestId } = await renderWrapped(); fireEvent.click(getByTestId('user-picker-owned')); await expect(screen.findByText(/Owned \(1\)/)).resolves.toBeInTheDocument(); - fireEvent.click(screen.getByTestId('user-picker-starred')); - await expect( - screen.findByText(/Starred \(0\)/), - ).resolves.toBeInTheDocument(); + // The "Starred" menu option should initially be disabled, since there + // aren't any starred entities. + await expect(screen.getByTestId('user-picker-starred')).toHaveAttribute( + 'aria-disabled', + 'true', + ); fireEvent.click(screen.getByTestId('user-picker-all')); await expect(screen.findByText(/All \(2\)/)).resolves.toBeInTheDocument(); @@ -274,6 +276,12 @@ describe('DefaultCatalogPage', () => { fireEvent.click(starredIcons[0]); await expect(screen.findByText(/All \(2\)/)).resolves.toBeInTheDocument(); + // Now that we've starred an entity, the "Starred" menu option should be + // enabled. + await expect(screen.getByTestId('user-picker-starred')).not.toHaveAttribute( + 'aria-disabled', + 'true', + ); fireEvent.click(screen.getByTestId('user-picker-starred')); await expect( screen.findByText(/Starred \(1\)/), diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 1373040c3c..5ad74a75c4 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-circleci +## 0.2.36 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.2.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.2.35 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 7a32e67b71..8fffeffc37 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.2.35", + "version": "0.2.36", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,9 +33,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -52,10 +52,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index f26431c739..08c66d50dd 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-cloudbuild +## 0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.2.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.2.33 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 3424d01ba9..6bb299418b 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cloudbuild", "description": "A Backstage plugin that integrates towards Google Cloud Build", - "version": "0.2.33", + "version": "0.2.34", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 8faa0d789d..def8cca7f0 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-code-coverage-backend +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + ## 0.1.20 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 433feaefa3..e01619671f 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage-backend", "description": "A Backstage backend plugin that helps you keep track of your code coverage", - "version": "0.1.20", + "version": "0.1.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", @@ -36,7 +36,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.35.0", diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index 06212ea885..7aef422166 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-code-coverage +## 0.1.24 + +### Patch Changes + +- 2ce5e4e0a7: Fixed a bug in the FileExplorer component which made it impossible to navigate upwards to a containing folder by clicking on the folder breadcrumb. +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.1.24-next.1 + +### Patch Changes + +- 2ce5e4e0a7: Fixed a bug in the FileExplorer component which made it impossible to navigate upwards to a containing folder by clicking on the folder breadcrumb. +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/plugin-catalog-react@0.6.13-next.1 + +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.1.23 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index bf7ce4e794..44e970d54a 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.1.23", + "version": "0.1.24", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,10 +23,10 @@ "dependencies": { "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -43,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/code-coverage/src/components/FileExplorer/FileExplorer.test.tsx b/plugins/code-coverage/src/components/FileExplorer/FileExplorer.test.tsx index 4163b22ac1..b59b3ad1fd 100644 --- a/plugins/code-coverage/src/components/FileExplorer/FileExplorer.test.tsx +++ b/plugins/code-coverage/src/components/FileExplorer/FileExplorer.test.tsx @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { groupByPath, buildFileStructure } from './FileExplorer'; +import { + groupByPath, + buildFileStructure, + getObjectsAtPath, +} from './FileExplorer'; const dummyFiles = [ { @@ -207,6 +211,28 @@ const coverageTableRowResults = { path: '', }; +const pathTestLeaf = { + files: [], + coverage: 1, + missing: 3, + tracked: 2, + path: 'file5', +}; + +const dummyFilesPathTest = { + files: [ + ...dummyFiles, + { + ...pathTestLeaf, + filename: 'dir3/dir7/file5', + }, + ], + coverage: 1, + missing: 1, + tracked: 1, + path: '', +}; + describe('groupByPath function', () => { it('should group files by their root directory,as per their filename', () => { expect(groupByPath(dummyFiles)).toStrictEqual(dummyDataGroupedByPath); @@ -220,3 +246,19 @@ describe('buildFileStructure function', () => { ); }); }); + +describe('getObjectsAtPath function', () => { + const structure = buildFileStructure(dummyFilesPathTest); + + it('should return the the dirs/files at the given path', () => { + expect(getObjectsAtPath(structure, ['dir3', 'dir7'])).toStrictEqual([ + pathTestLeaf, + ]); + }); + + it('should return undefined for a nonexistent path', () => { + expect( + getObjectsAtPath(structure, ['dir3', 'doesnt-exist']), + ).toBeUndefined(); + }); +}); diff --git a/plugins/code-coverage/src/components/FileExplorer/FileExplorer.tsx b/plugins/code-coverage/src/components/FileExplorer/FileExplorer.tsx index 790de6e00f..3d41a1ff64 100644 --- a/plugins/code-coverage/src/components/FileExplorer/FileExplorer.tsx +++ b/plugins/code-coverage/src/components/FileExplorer/FileExplorer.tsx @@ -131,6 +131,17 @@ const formatInitialData = (value: any) => { }); }; +export const getObjectsAtPath = ( + curData: CoverageTableRow | undefined, + path: string[], +): CoverageTableRow[] | undefined => { + let data = curData?.files; + for (const fragment of path) { + data = data?.find(d => d.path === fragment)?.files; + } + return data; +}; + export const FileExplorer = () => { const { entity } = useEntity(); const [curData, setCurData] = useState(); @@ -175,14 +186,10 @@ export const FileExplorer = () => { } }; - const moveUpIntoPath = (path: string) => { - const pathArray = path.split('/').filter(p => p.length); - let data = curData?.files; - pathArray.forEach(p => { - data = data?.find(d => d.path === p)?.files; - }); - setCurPath(path); - setTableData(data); + const moveUpIntoPath = (idx: number) => { + const path = curPath.split('/').slice(0, idx + 1); + setCurPath(path.join('/')); + setTableData(getObjectsAtPath(curData, path.slice(1))); }; const columns: TableColumn[] = [ @@ -270,8 +277,8 @@ export const FileExplorer = () => { color: `${idx !== lastPathElementIndex && 'lightblue'}`, cursor: `${idx !== lastPathElementIndex && 'pointer'}`, }} - onKeyDown={() => moveUpIntoPath(pathElement)} - onClick={() => moveUpIntoPath(pathElement)} + onKeyDown={() => moveUpIntoPath(idx)} + onClick={() => moveUpIntoPath(idx)} > {pathElement || 'root'}
diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index c83111f01b..9c6ff062a7 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-config-schema +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.1.19 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index e1602227a6..26a583fa95 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-config-schema", "description": "A Backstage plugin that lets you browse the configuration schema of your app", - "version": "0.1.19", + "version": "0.1.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", "@backstage/theme": "^0.2.14", @@ -38,10 +38,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 0144c3e74c..93a9bbdb19 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-cost-insights +## 0.11.19 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.11.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.11.18 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 6ee59ee652..288464129d 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cost-insights", "description": "A Backstage plugin that helps you keep track of your cloud spend", - "version": "0.11.18", + "version": "0.11.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "dependencies": { "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", @@ -57,10 +57,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 02705b3476..7bd9edd854 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -32,9 +32,9 @@ "@backstage/core-plugin-api": "^0.6.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1-next.1", + "@backstage/dev-utils": "^0.2.20-next.1", + "@backstage/test-utils": "^0.2.4-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 8e1487383f..03c200c5e8 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-explore +## 0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.3.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.3.27 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index f88ffef654..424f6abf2f 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore", "description": "A Backstage plugin for building an exploration page of your software ecosystem", - "version": "0.3.27", + "version": "0.3.28", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/plugin-explore-react": "^0.0.11", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", @@ -50,10 +50,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md index 153f86f858..c441693df6 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-firehydrant +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.1.13 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 5a869b7f9b..4d1cc6ba44 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-firehydrant", "description": "A Backstage plugin that integrates towards FireHydrant", - "version": "0.1.13", + "version": "0.1.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,9 +22,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -36,10 +36,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 3b429d6cca..d01d581db8 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-fossa +## 0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.2.28 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 2a3160e4ab..9ce1008659 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-fossa", "description": "A Backstage plugin that integrates towards FOSSA", - "version": "0.2.28", + "version": "0.2.29", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -50,10 +50,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index ea8da0ec9f..77b57867c5 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-gcp-projects +## 0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.3.15 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index b9359af966..0df05ae7b1 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gcp-projects", "description": "A Backstage plugin that helps you manage projects in GCP", - "version": "0.3.15", + "version": "0.3.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", @@ -44,10 +44,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index cb074928b8..605f27322e 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-git-release-manager +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.3.9 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index dbc50e6921..b2acf96ccf 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-git-release-manager", "description": "A Backstage plugin that helps you manage releases in git", - "version": "0.3.9", + "version": "0.3.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/integration": "^0.7.2", "@backstage/theme": "^0.2.14", @@ -40,10 +40,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/git-release-manager/src/features/Features.test.tsx b/plugins/git-release-manager/src/features/Features.test.tsx index 28bd9b6310..8f9e3544f2 100644 --- a/plugins/git-release-manager/src/features/Features.test.tsx +++ b/plugins/git-release-manager/src/features/Features.test.tsx @@ -55,7 +55,7 @@ describe('Features', () => { expect(getByTestId(TEST_IDS.info.info)).toMatchInlineSnapshot(`
{ kind: 'User', metadata: { name: 'calum.leavy', + description: 'Super awesome human', }, spec: { profile: { @@ -73,5 +74,6 @@ describe('UserSummary Test', () => { 'href', '/catalog/default/group/ExampleGroup', ); + expect(rendered.getByText('Super awesome human')).toBeInTheDocument(); }); }); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 298819ed09..0997e0a4d8 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -61,7 +61,7 @@ export const UserProfileCard = ({ } const { - metadata: { name: metaName }, + metadata: { name: metaName, description }, spec: { profile }, } = user; const displayName = profile?.displayName ?? metaName; @@ -71,7 +71,11 @@ export const UserProfileCard = ({ }); return ( - } variant={variant}> + } + subheader={description} + variant={variant} + > diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 5cccf1debe..4de0112481 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-pagerduty +## 0.3.24 + +### Patch Changes + +- 5a459626bc: Fix change events tab error when change events exist +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.3.24-next.0 + +### Patch Changes + +- 5a459626bc: Fix change events tab error when change events exist +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.3.23 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index c01c2ce2ba..96b1c6738b 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-pagerduty", "description": "A Backstage plugin that integrates towards PagerDuty", - "version": "0.3.23", + "version": "0.3.24", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 7719376104..5a2aaa72bf 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-permission-backend +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0 + - @backstage/backend-common@0.10.6 + - @backstage/plugin-permission-node@0.4.2 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.1 + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-permission-node@0.4.2-next.1 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.0 + - @backstage/plugin-permission-node@0.4.2-next.0 + ## 0.4.1 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 691d7eeefc..8c0bdb93c4 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.4.1", + "version": "0.4.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,12 +19,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", - "@backstage/plugin-auth-backend": "^0.8.0", + "@backstage/plugin-auth-backend": "^0.9.0", "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/plugin-permission-node": "^0.4.1", + "@backstage/plugin-permission-node": "^0.4.2", "@types/express": "*", "dataloader": "^2.0.0", "express": "^4.17.1", @@ -36,7 +36,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 69af3054c0..103f342490 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -45,7 +45,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1-next.0", "@types/jest": "^26.0.7", "msw": "^0.35.0" } diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index a552e2c67b..c4690777f6 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-permission-node +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0 + - @backstage/backend-common@0.10.6 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.1 + - @backstage/backend-common@0.10.6-next.0 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.0 + ## 0.4.1 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 11fb27f34e..a5b6adabc2 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.4.1", + "version": "0.4.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", - "@backstage/plugin-auth-backend": "^0.8.0", + "@backstage/plugin-auth-backend": "^0.9.0", "@backstage/plugin-permission-common": "^0.4.0", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -40,7 +40,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index dd3a2c696f..b1e0350dcc 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -32,15 +32,16 @@ "@backstage/plugin-permission-common": "^0.4.0", "cross-fetch": "^3.0.6", "react-router": "6.0.0-beta.0", - "react-use": "^17.2.4" + "react-use": "^17.2.4", + "swr": "^1.1.2" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1-next.1", + "@backstage/test-utils": "^0.2.4-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@types/jest": "^26.0.7" diff --git a/plugins/permission-react/src/hooks/usePermission.test.tsx b/plugins/permission-react/src/hooks/usePermission.test.tsx index 4cf0bc6530..d6c5b2ed33 100644 --- a/plugins/permission-react/src/hooks/usePermission.test.tsx +++ b/plugins/permission-react/src/hooks/usePermission.test.tsx @@ -19,9 +19,8 @@ import { render } from '@testing-library/react'; import { usePermission } from './usePermission'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { TestApiProvider } from '@backstage/test-utils'; -import { permissionApiRef } from '../apis'; - -const mockAuthorize = jest.fn(); +import { PermissionApi, permissionApiRef } from '../apis'; +import { SWRConfig } from 'swr'; const permission = { name: 'access.something', @@ -39,49 +38,48 @@ const TestComponent: FC = () => { ); }; -describe('usePermission', () => { - it('Returns loading when permissionApi has not yet responded.', () => { - mockAuthorize.mockReturnValueOnce(new Promise(() => {})); - - const { getByText } = render( - +function renderComponent(mockApi: PermissionApi) { + return render( + new Map() }}> + - , - ); + + , + , + ); +} - expect(mockAuthorize).toHaveBeenCalledWith({ permission }); +describe('usePermission', () => { + const mockPermissionApi = { authorize: jest.fn() }; + + it('Returns loading when permissionApi has not yet responded.', () => { + mockPermissionApi.authorize.mockReturnValueOnce(new Promise(() => {})); + + const { getByText } = renderComponent(mockPermissionApi); + + expect(mockPermissionApi.authorize).toHaveBeenCalledWith({ permission }); expect(getByText('loading')).toBeTruthy(); }); it('Returns allowed when permissionApi allows authorization.', async () => { - mockAuthorize.mockResolvedValueOnce({ result: AuthorizeResult.ALLOW }); + mockPermissionApi.authorize.mockResolvedValueOnce({ + result: AuthorizeResult.ALLOW, + }); - const { findByText } = render( - - - , - ); + const { findByText } = renderComponent(mockPermissionApi); - expect(mockAuthorize).toHaveBeenCalledWith({ permission }); + expect(mockPermissionApi.authorize).toHaveBeenCalledWith({ permission }); expect(await findByText('content')).toBeTruthy(); }); it('Returns not allowed when permissionApi denies authorization.', async () => { - mockAuthorize.mockResolvedValueOnce({ result: AuthorizeResult.DENY }); + mockPermissionApi.authorize.mockResolvedValueOnce({ + result: AuthorizeResult.DENY, + }); - const { findByText } = render( - - - , - ); + const { findByText } = renderComponent(mockPermissionApi); - expect(mockAuthorize).toHaveBeenCalledWith({ permission }); + expect(mockPermissionApi.authorize).toHaveBeenCalledWith({ permission }); await expect(findByText('content')).rejects.toThrowError(); }); }); diff --git a/plugins/permission-react/src/hooks/usePermission.ts b/plugins/permission-react/src/hooks/usePermission.ts index 45136d6697..cc27f82c59 100644 --- a/plugins/permission-react/src/hooks/usePermission.ts +++ b/plugins/permission-react/src/hooks/usePermission.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import useAsync from 'react-use/lib/useAsync'; import { useApi } from '@backstage/core-plugin-api'; import { permissionApiRef } from '../apis'; import { AuthorizeResult, Permission, } from '@backstage/plugin-permission-common'; +import useSWR from 'swr'; /** @public */ export type AsyncPermissionResult = { @@ -30,9 +30,16 @@ export type AsyncPermissionResult = { }; /** - * React hook utlity for authorization. Given a {@link @backstage/plugin-permission-common#Permission} and an optional - * resourceRef, it will return whether or not access is allowed (for the given resource, if resourceRef is provided). See - * {@link @backstage/plugin-permission-common/PermissionClient#authorize} for more details. + * React hook utility for authorization. Given a + * {@link @backstage/plugin-permission-common#Permission} and an optional + * resourceRef, it will return whether or not access is allowed (for the given + * resource, if resourceRef is provided). See + * {@link @backstage/plugin-permission-common/PermissionClient#authorize} for + * more details. + * + * Note: This hook uses stale-while-revalidate to help avoid flicker in UI + * elements that would be conditionally rendered based on the `allowed` result + * of this hook. * @public */ export const usePermission = ( @@ -40,21 +47,16 @@ export const usePermission = ( resourceRef?: string, ): AsyncPermissionResult => { const permissionApi = useApi(permissionApiRef); - - const { loading, error, value } = useAsync(async () => { - const { result } = await permissionApi.authorize({ - permission, - resourceRef, - }); - + const { data, error } = useSWR({ permission, resourceRef }, async args => { + const { result } = await permissionApi.authorize(args); return result; - }, [permissionApi, permission, resourceRef]); + }); - if (loading) { - return { loading: true, allowed: false }; - } if (error) { return { error, loading: false, allowed: false }; } - return { loading: false, allowed: value === AuthorizeResult.ALLOW }; + if (data === undefined) { + return { loading: true, allowed: false }; + } + return { loading: false, allowed: data === AuthorizeResult.ALLOW }; }; diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index d70862e1a2..4fb4328974 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/plugin-proxy-backend +## 0.2.17 + +### Patch Changes + +- 332d3decb2: Adds a new option `skipInvalidTargets` for the proxy `createRouter` which allows the proxy backend to be started with an invalid proxy configuration. If configured, it will simply skip the failed proxy and mount the other valid proxies. + + To configure it to pass by failing proxies: + + ``` + const router = await createRouter({ + config, + logger, + discovery, + skipInvalidProxies: true, + }); + ``` + + If you would like it to fail if a proxy is configured badly: + + ``` + const router = await createRouter({ + config, + logger, + discovery, + }); + ``` + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + +## 0.2.17-next.0 + +### Patch Changes + +- 332d3decb2: Adds a new option `skipInvalidTargets` for the proxy `createRouter` which allows the proxy backend to be started with an invalid proxy configuration. If configured, it will simply skip the failed proxy and mount the other valid proxies. + + To configure it to pass by failing proxies: + + ``` + const router = await createRouter({ + config, + logger, + discovery, + skipInvalidProxies: true, + }); + ``` + + If you would like it to fail if a proxy is configured badly: + + ``` + const router = await createRouter({ + config, + logger, + discovery, + }); + ``` + ## 0.2.16 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 78e6a2e553..3d7d03794b 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-proxy-backend", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", - "version": "0.2.16", + "version": "0.2.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -43,7 +43,7 @@ "yup": "^0.32.9" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index a8feeee4a6..9f6c657935 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-rollbar-backend +## 0.1.20 + +### Patch Changes + +- 91faf87aaf: chore(deps): bump `camelcase-keys` from 6.2.2 to 7.0.1 +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + +## 0.1.20-next.0 + +### Patch Changes + +- 91faf87aaf: chore(deps): bump `camelcase-keys` from 6.2.2 to 7.0.1 + ## 0.1.19 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index eab94b6f84..81f288c9e8 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar-backend", "description": "A Backstage backend plugin that integrates towards Rollbar", - "version": "0.1.19", + "version": "0.1.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", "@types/express": "^4.17.6", "camelcase-keys": "^7.0.1", @@ -48,8 +48,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/test-utils": "^0.2.4", "@types/supertest": "^2.0.8", "msw": "^0.36.3", "supertest": "^6.1.3" diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index d414e3d841..fdb3feb3ea 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-rollbar +## 0.3.25 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.3.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.3.24 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index dc7ede62cf..35e660a650 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar", "description": "A Backstage plugin that integrates towards Rollbar", - "version": "0.3.24", + "version": "0.3.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,9 +33,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -50,10 +50,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index fd0bf3e177..231538cbc1 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.15.23 + - @backstage/backend-common@0.10.6 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-scaffolder-backend@0.15.23-next.1 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.15.23-next.0 + ## 0.1.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index e1571c95b1..e7e2d7b469 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", - "version": "0.1.9", + "version": "0.1.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", - "@backstage/plugin-scaffolder-backend": "^0.15.22", + "@backstage/plugin-scaffolder-backend": "^0.15.23", "@backstage/config": "^0.1.13", "@backstage/types": "^0.1.1", "command-exists": "^1.2.9", @@ -32,7 +32,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/jest": "^26.0.7", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 4615185fb4..bb78b9e5fd 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.15.23 + - @backstage/backend-common@0.10.6 + +## 0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-scaffolder-backend@0.15.23-next.1 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.15.23-next.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 2779b42631..36d68bf46d 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", "description": "A module for the scaffolder backend that lets you template projects using Rails", - "version": "0.2.4", + "version": "0.2.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", - "@backstage/plugin-scaffolder-backend": "^0.15.22", + "@backstage/backend-common": "^0.10.6", + "@backstage/plugin-scaffolder-backend": "^0.15.23", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", @@ -31,7 +31,7 @@ "fs-extra": "^9.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/command-exists": "^1.2.0", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 02c2a7f7bd..517cd20ca0 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.15.23 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.15.23-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index fe2e7dfc6a..b43d0c50bb 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,13 +21,13 @@ }, "dependencies": { "@backstage/config": "^0.1.13", - "@backstage/plugin-scaffolder-backend": "^0.15.21", + "@backstage/plugin-scaffolder-backend": "^0.15.23", "@backstage/types": "^0.1.1", "winston": "^3.2.1", "yeoman-environment": "^3.6.0" }, "devDependencies": { - "@backstage/backend-common": "^0.10.4", + "@backstage/backend-common": "^0.10.6", "@types/jest": "^26.0.7" }, "files": [ diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 9941990066..b6061e8a38 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-scaffolder-backend +## 0.15.23 + +### Patch Changes + +- 2e0dbb0e50: Migrate from deprecated package @octokit/rest to octokit +- c95df1631e: Added support for templating secrets into actions input, and also added an extra `token` input argument to all publishers to provide a token that would override the `integrations.config`. + You can find more information over at [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#using-the-users-oauth-token) +- Updated dependencies + - @backstage/plugin-catalog-backend@0.21.2 + - @backstage/backend-common@0.10.6 + - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.1.10 + +## 0.15.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-catalog-backend@0.21.2-next.1 + - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.1.10-next.1 + +## 0.15.23-next.0 + +### Patch Changes + +- 2e0dbb0e50: Migrate from deprecated package @octokit/rest to octokit +- Updated dependencies + - @backstage/plugin-catalog-backend@0.21.2-next.0 + - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.1.10-next.0 + ## 0.15.22 ### Patch Changes diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index cfe8f5d2b0..5e7b5a428b 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -299,7 +299,12 @@ export class OctokitProvider { githubCredentialsProvider?: GithubCredentialsProvider, ); // Warning: (ae-forgotten-export) The symbol "OctokitIntegration" needs to be exported by the entry point index.d.ts - getOctokit(repoUrl: string): Promise; + getOctokit( + repoUrl: string, + options?: { + token?: string; + }, + ): Promise; } // @public diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index efcaf2b712..335d85327a 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "0.15.22", + "version": "0.15.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,15 +31,15 @@ "build:assets": "node scripts/build-nunjucks.js" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", - "@backstage/plugin-catalog-backend": "^0.21.1", + "@backstage/plugin-catalog-backend": "^0.21.2", "@backstage/plugin-scaffolder-common": "^0.1.3", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.9", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.10", "@backstage/types": "^0.1.1", "@gitbeaker/core": "^34.6.0", "@gitbeaker/node": "^35.1.0", @@ -73,8 +73,8 @@ "vm2": "^3.9.5" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/test-utils": "^0.2.4", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.test.ts index 0d4353c857..e2449917ab 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.test.ts @@ -74,4 +74,15 @@ describe('getOctokit', () => { expect(owner).toBe('owner'); expect(repo).toBe('bob'); }); + + it('should return an octokit client with the passed in token if it is provided', async () => { + const { client, token, owner, repo } = await octokitProvider.getOctokit( + 'github.com?repo=bob&owner=owner', + { token: 'tokenlols2' }, + ); + expect(client).toBeDefined(); + expect(token).toBe('tokenlols2'); + expect(owner).toBe('owner'); + expect(repo).toBe('bob'); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts index a3eeec4373..3ac66a0023 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts @@ -52,7 +52,10 @@ export class OctokitProvider { * * @param repoUrl - Repository URL */ - async getOctokit(repoUrl: string): Promise { + async getOctokit( + repoUrl: string, + options?: { token?: string }, + ): Promise { const { owner, repo, host } = parseRepoUrl(repoUrl, this.integrations); if (!owner) { @@ -65,7 +68,19 @@ export class OctokitProvider { throw new InputError(`No integration for host ${host}`); } - // TODO(blam): Consider changing this API to have owner, repo interface instead of URL as the it's + // Short circuit the internal Github Token provider the token provided + // by the action or the caller. + if (options?.token) { + const client = new Octokit({ + auth: options.token, + baseUrl: integrationConfig.apiBaseUrl, + previews: ['nebula-preview'], + }); + + return { client, token: options.token, owner, repo }; + } + + // TODO(blam): Consider changing this API to have owner, repoo interface instead of URL as the it's // needless to create URL and then parse again the other side. const { token } = await this.githubCredentialsProvider.getCredentials({ url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts index 1f7f0f60b9..0c8b4d4aab 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts @@ -37,6 +37,7 @@ export function createGithubActionsDispatchAction(options: { workflowId: string; branchOrTagName: string; workflowInputs?: { [key: string]: string }; + token?: string; }>({ id: 'github:actions:dispatch', description: @@ -68,18 +69,31 @@ export function createGithubActionsDispatchAction(options: { 'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10. ', type: 'object', }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The GITHUB_TOKEN to use for authorization to GitHub', + }, }, }, }, async handler(ctx) { - const { repoUrl, workflowId, branchOrTagName, workflowInputs } = - ctx.input; + const { + repoUrl, + workflowId, + branchOrTagName, + workflowInputs, + token: providedToken, + } = ctx.input; ctx.logger.info( `Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`, ); - const { client, owner, repo } = await octokitProvider.getOctokit(repoUrl); + const { client, owner, repo } = await octokitProvider.getOctokit( + repoUrl, + { token: providedToken }, + ); await client.rest.actions.createWorkflowDispatch({ owner, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts index 39e8c3aa70..660c7793f2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts @@ -47,6 +47,7 @@ export function createGithubWebhookAction(options: { active?: boolean; contentType?: ContentType; insecureSsl?: boolean; + token?: string; }>({ id: 'github:webhook', description: 'Creates webhook for a repository on GitHub.', @@ -107,6 +108,11 @@ export function createGithubWebhookAction(options: { type: 'boolean', description: `Determines whether the SSL certificate of the host for url will be verified when delivering payloads. Default 'false'`, }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The GITHUB_TOKEN to use for authorization to GitHub', + }, }, }, }, @@ -119,11 +125,15 @@ export function createGithubWebhookAction(options: { active = true, contentType = 'form', insecureSsl = false, + token: providedToken, } = ctx.input; ctx.logger.info(`Creating webhook ${webhookUrl} for repo ${repoUrl}`); - const { client, owner, repo } = await octokitProvider.getOctokit(repoUrl); + const { client, owner, repo } = await octokitProvider.getOctokit( + repoUrl, + { token: providedToken }, + ); try { const insecure_ssl = insecureSsl ? '1' : '0'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts index cee99e1646..9247109121 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts @@ -119,6 +119,32 @@ describe('publish:azure', () => { ).rejects.toThrow(/Unable to create the repository/); }); + it('should not throw if there is a token provided through ctx.input', async () => { + mockGitClient.createRepository.mockImplementation(() => ({ + remoteUrl: 'http://google.com', + })); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'myazurehostnotoken.com?repo=bob&owner=owner&organization=org', + token: 'lols', + }, + }); + + expect(WebApi).toHaveBeenCalledWith( + 'https://myazurehostnotoken.com/org', + expect.any(Function), + ); + + expect(mockGitClient.createRepository).toHaveBeenCalledWith( + { + name: 'bob', + }, + 'owner', + ); + }); + it('should throw if there is no remoteUrl returned', async () => { mockGitClient.createRepository.mockImplementation(() => ({ remoteUrl: null, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts index 7e394a8d8b..4f67a0e45f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts @@ -34,6 +34,7 @@ export function createPublishAzureAction(options: { description?: string; defaultBranch?: string; sourcePath?: string; + token?: string; }>({ id: 'publish:azure', description: @@ -57,10 +58,16 @@ export function createPublishAzureAction(options: { description: `Sets the default branch on the repository. The default value is 'master'`, }, sourcePath: { - title: + title: 'Source Path', + description: 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', type: 'string', }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to Azure', + }, }, }, output: { @@ -98,12 +105,13 @@ export function createPublishAzureAction(options: { `No matching integration configuration for host ${host}, please check your integrations config`, ); } - if (!integrationConfig.config.token) { + + if (!integrationConfig.config.token && !ctx.input.token) { throw new InputError(`No token provided for Azure Integration ${host}`); } - const authHandler = getPersonalAccessTokenHandler( - integrationConfig.config.token, - ); + + const token = ctx.input.token ?? integrationConfig.config.token!; + const authHandler = getPersonalAccessTokenHandler(token); const webApi = new WebApi(`https://${host}/${organization}`, authHandler); const client = await webApi.getGitApi(); @@ -139,7 +147,7 @@ export function createPublishAzureAction(options: { defaultBranch, auth: { username: 'notempty', - password: integrationConfig.config.token, + password: token, }, logger: ctx.logger, commitMessage: config.getOptionalString( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts index 2678abd9dd..3d14710f3e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts @@ -194,6 +194,45 @@ describe('publish:bitbucket', () => { }); }); + it('should work if the token is provided through ctx.input', async () => { + expect.assertions(2); + server.use( + rest.post( + 'https://notoken.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer lols'); + expect(req.body).toEqual({ public: false, name: 'repo' }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ); + await action.handler({ + ...mockContext, + input: { + repoUrl: 'notoken.bitbucket.com?project=project&repo=repo', + token: 'lols', + }, + }); + }); + describe('LFS for hosted bitbucket', () => { const repoCreationResponse = { links: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts index fbfe87b076..8ba8592642 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts @@ -205,6 +205,7 @@ export function createPublishBitbucketAction(options: { repoVisibility: 'private' | 'public'; sourcePath?: string; enableLFS: boolean; + token?: string; }>({ id: 'publish:bitbucket', description: @@ -233,15 +234,22 @@ export function createPublishBitbucketAction(options: { description: `Sets the default branch on the repository. The default value is 'master'`, }, sourcePath: { - title: + title: 'Source Path', + description: 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', type: 'string', }, enableLFS: { - title: + title: 'Enable LFS?', + description: 'Enable LFS for the repository. Only available for hosted Bitbucket.', type: 'boolean', }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to BitBucket', + }, }, }, output: { @@ -296,7 +304,12 @@ export function createPublishBitbucketAction(options: { ); } - const authorization = getAuthorizationHeader(integrationConfig.config); + const authorization = getAuthorizationHeader( + ctx.input.token + ? { host: integrationConfig.config.host, token: ctx.input.token } + : integrationConfig.config, + ); + const apiBaseUrl = integrationConfig.config.apiBaseUrl; const createMethod = @@ -320,17 +333,28 @@ export function createPublishBitbucketAction(options: { email: config.getOptionalString('scaffolder.defaultAuthor.email'), }; - await initRepoAndPush({ - dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), - remoteUrl, - auth: { + let auth; + + if (ctx.input.token) { + auth = { + username: 'x-token-auth', + password: ctx.input.token, + }; + } else { + auth = { username: integrationConfig.config.username ? integrationConfig.config.username : 'x-token-auth', password: integrationConfig.config.appPassword ? integrationConfig.config.appPassword : integrationConfig.config.token ?? '', - }, + }; + } + + await initRepoAndPush({ + dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), + remoteUrl, + auth, defaultBranch, logger: ctx.logger, commitMessage: config.getOptionalString( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index 081f9616ac..139fad04d4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -52,6 +52,7 @@ export function createPublishGithubAction(options: { requireCodeOwnerReviews?: boolean; repoVisibility: 'private' | 'internal' | 'public'; collaborators: Collaborator[]; + token?: string; topics?: string[]; }>({ id: 'publish:github', @@ -77,7 +78,8 @@ export function createPublishGithubAction(options: { type: 'string', }, requireCodeOwnerReviews: { - title: + title: 'Require CODEOWNER Reviews?', + description: 'Require an approved review in PR including files with a designated Code Owner', type: 'boolean', }, @@ -92,7 +94,8 @@ export function createPublishGithubAction(options: { description: `Sets the default branch on the repository. The default value is 'master'`, }, sourcePath: { - title: + title: 'Source Path', + description: 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', type: 'string', }, @@ -116,6 +119,11 @@ export function createPublishGithubAction(options: { }, }, }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to GitHub', + }, topics: { title: 'Topics', type: 'array', @@ -149,10 +157,12 @@ export function createPublishGithubAction(options: { defaultBranch = 'master', collaborators, topics, + token: providedToken, } = ctx.input; const { client, token, owner, repo } = await octokitProvider.getOctokit( repoUrl, + { token: providedToken }, ); const user = await client.rest.users.getByUsername({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index 841d4c7a82..80333c4966 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -55,6 +55,7 @@ export type GithubPullRequestActionInput = { repoUrl: string; targetPath?: string; sourcePath?: string; + token?: string; }; export type ClientFactoryInput = { @@ -63,6 +64,7 @@ export type ClientFactoryInput = { host: string; owner: string; repo: string; + token?: string; }; export const defaultClientFactory = async ({ @@ -71,13 +73,22 @@ export const defaultClientFactory = async ({ owner, repo, host = 'github.com', + token: providedToken, }: ClientFactoryInput): Promise => { const integrationConfig = integrations.github.byHost(host)?.config; + const OctokitPR = Octokit.plugin(createPullRequest); if (!integrationConfig) { throw new InputError(`No integration for host ${host}`); } + if (providedToken) { + return new OctokitPR({ + auth: providedToken, + baseUrl: integrationConfig.apiBaseUrl, + }); + } + const credentialsProvider = githubCredentialsProvider || SingleInstanceGithubCredentialsProvider.create(integrationConfig); @@ -94,8 +105,6 @@ export const defaultClientFactory = async ({ ); } - const OctokitPR = Octokit.plugin(createPullRequest); - return new OctokitPR({ auth: token, baseUrl: integrationConfig.apiBaseUrl, @@ -151,6 +160,11 @@ export const createPublishGithubPullRequestAction = ({ title: 'Repository Subdirectory', description: 'Subdirectory of repository to apply changes to', }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to GitHub', + }, }, }, output: { @@ -173,6 +187,7 @@ export const createPublishGithubPullRequestAction = ({ description, targetPath, sourcePath, + token: providedToken, } = ctx.input; const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); @@ -189,7 +204,9 @@ export const createPublishGithubPullRequestAction = ({ host, owner, repo, + token: providedToken, }); + const fileRoot = sourcePath ? resolveSafeChildPath(ctx.workspacePath, sourcePath) : ctx.workspacePath; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts index 2cd27da359..a32fea8830 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts @@ -96,6 +96,28 @@ describe('publish:gitlab', () => { ).rejects.toThrow(/No token available for host/); }); + it('should work when there is a token provided through ctx.input', async () => { + mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 }); + mockGitlabClient.Projects.create.mockResolvedValue({ + http_url_to_repo: 'http://mockurl.git', + }); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'hosted.gitlab.com?repo=bob&owner=owner', + token: 'token', + }, + }); + + expect(mockGitlabClient.Namespaces.show).toHaveBeenCalledWith('owner'); + expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({ + namespace_id: 1234, + name: 'bob', + visibility: 'private', + }); + }); + it('should call the correct Gitlab APIs when the owner is an organization', async () => { mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 }); mockGitlabClient.Projects.create.mockResolvedValue({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts index 5941f52746..a04766f941 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts @@ -33,6 +33,7 @@ export function createPublishGitlabAction(options: { defaultBranch?: string; repoVisibility: 'private' | 'internal' | 'public'; sourcePath?: string; + token?: string; }>({ id: 'publish:gitlab', description: @@ -57,10 +58,16 @@ export function createPublishGitlabAction(options: { description: `Sets the default branch on the repository. The default value is 'master'`, }, sourcePath: { - title: + title: 'Source Path', + description: 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', type: 'string', }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to GitLab', + }, }, }, output: { @@ -100,13 +107,15 @@ export function createPublishGitlabAction(options: { ); } - if (!integrationConfig.config.token) { + if (!integrationConfig.config.token && !ctx.input.token) { throw new InputError(`No token available for host ${host}`); } + const token = ctx.input.token || integrationConfig.config.token!; + const client = new Gitlab({ host: integrationConfig.config.baseUrl, - token: integrationConfig.config.token, + token, }); let { id: targetNamespace } = (await client.Namespaces.show(owner)) as { @@ -140,7 +149,7 @@ export function createPublishGitlabAction(options: { defaultBranch, auth: { username: 'oauth2', - password: integrationConfig.config.token, + password: token, }, logger: ctx.logger, commitMessage: config.getOptionalString( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts index 2ac369249b..281b72789f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts @@ -31,6 +31,7 @@ export type GitlabMergeRequestActionInput = { description: string; branchName: string; targetPath: string; + token?: string; }; export const createPublishGitlabMergeRequestAction = (options: { @@ -75,6 +76,11 @@ export const createPublishGitlabMergeRequestAction = (options: { title: 'Repository Subdirectory', description: 'Subdirectory of repository to apply changes to', }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to GitLab', + }, }, }, output: { @@ -107,13 +113,15 @@ export const createPublishGitlabMergeRequestAction = (options: { ); } - if (!integrationConfig.config.token) { + if (!integrationConfig.config.token && !ctx.input.token) { throw new InputError(`No token available for host ${host}`); } + const token = ctx.input.token ?? integrationConfig.config.token!; + const api = new Gitlab({ host: integrationConfig.config.baseUrl, - token: integrationConfig.config.token, + token, }); const fileRoot = ctx.workspacePath; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 161d5b865b..2fc6b99406 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -497,6 +497,60 @@ describe('DefaultWorkflowRunner', () => { expect.objectContaining({ secrets: { foo: 'bar' } }), ); }); + + it('should be able to template secrets into the input of an action', async () => { + const task = createMockTaskWithSpec( + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + b: '${{ secrets.foo }}', + }, + }, + ], + output: {}, + parameters: {}, + }, + { foo: 'bar' }, + ); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ input: { b: 'bar' } }), + ); + }); + + it('does not allow templating of secrets as an output', async () => { + const task = createMockTaskWithSpec( + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + b: '${{ secrets.foo }}', + }, + }, + ], + output: { + b: '${{ secrets.foo }}', + }, + parameters: {}, + }, + { foo: 'bar' }, + ); + + const executedTask = await runner.execute(task); + + expect(executedTask.output.b).toBeUndefined(); + }); }); describe('filters', () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 5089998834..889e94d000 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -53,6 +53,7 @@ type TemplateContext = { steps: { [stepName: string]: { output: { [outputName: string]: JsonValue } }; }; + secrets?: Record; }; const isValidTaskSpec = (taskSpec: TaskSpec): taskSpec is TaskSpecV1beta3 => { @@ -231,8 +232,14 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const action = this.options.actionRegistry.get(step.action); const { taskLogger, streamLogger } = createStepLogger({ task, step }); + // Secrets are only passed when templating the input to actions for security reasons const input = - (step.input && this.render(step.input, context, renderTemplate)) ?? + (step.input && + this.render( + step.input, + { ...context, secrets: task.secrets ?? {} }, + renderTemplate, + )) ?? {}; if (action.schema?.input) { diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index d37219fe97..c1d99b987b 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -40,6 +40,6 @@ "@backstage/types": "^0.1.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0" + "@backstage/cli": "^0.13.1-next.0" } } diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 81fd54b5aa..dfbe114f82 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-scaffolder +## 0.12.1 + +### Patch Changes + +- ba59832aed: Permission the Register Existing Component button +- cee44ad289: Added the ability to collect users `oauth` token from the `RepoUrlPicker` for use in the template manifest +- a681cb9c2f: Make linkTarget configurable for MarkdownContent component +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/plugin-catalog-common@0.1.2 + - @backstage/integration-react@0.1.20 + +## 0.12.1-next.1 + +### Patch Changes + +- ba59832aed: Permission the Register Existing Component button +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/plugin-catalog-react@0.6.13-next.1 + - @backstage/plugin-catalog-common@0.1.2-next.0 + +## 0.12.1-next.0 + +### Patch Changes + +- a681cb9c2f: Make linkTarget configurable for MarkdownContent component +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/integration-react@0.1.20-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.12.0 ### Minor Changes diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 12fed2326a..f8ae64cd82 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -164,6 +164,16 @@ export interface RepoUrlPickerUiOptions { allowedHosts?: string[]; // (undocumented) allowedOwners?: string[]; + // (undocumented) + requestUserCredentials?: { + secretsKey: string; + additionalScopes?: { + github?: string[]; + gitlab?: string[]; + bitbucket?: string[]; + azure?: string[]; + }; + }; } // @public @@ -327,4 +337,9 @@ export const TextValuePicker: ({ idSchema, placeholder, }: FieldProps) => JSX.Element; + +// @public +export const useTemplateSecrets: () => { + setSecret: (input: Record) => void; +}; ``` diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index a5c6e78f13..60852e3b93 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "0.12.0", + "version": "0.12.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,12 +34,14 @@ "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", - "@backstage/integration-react": "^0.1.19", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/integration-react": "^0.1.20", + "@backstage/plugin-catalog-common": "^0.1.2", + "@backstage/plugin-catalog-react": "^0.6.13", + "@backstage/plugin-permission-react": "^0.3.0", "@backstage/plugin-scaffolder-common": "^0.1.3", "@backstage/theme": "^0.2.14", "@backstage/types": "^0.1.1", @@ -67,11 +69,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/plugin-catalog": "^0.7.10", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/plugin-catalog": "^0.7.11", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index e7c93cebbb..601844780d 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -21,6 +21,7 @@ import { ScaffolderPage } from './ScaffolderPage'; import { TemplatePage } from './TemplatePage'; import { TaskPage } from './TaskPage'; import { ActionsPage } from './ActionsPage'; +import { SecretsContextProvider } from './secrets/SecretsContext'; import { FieldExtensionOptions, @@ -83,7 +84,11 @@ export const Router = ({ /> } + element={ + + + + } /> } /> } /> diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index c147c8e118..116b7ed82a 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -37,6 +37,8 @@ import React, { ComponentType } from 'react'; import { registerComponentRouteRef } from '../../routes'; import { TemplateList } from '../TemplateList'; import { TemplateTypePicker } from '../TemplateTypePicker'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; +import { usePermission } from '@backstage/plugin-permission-react'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -72,6 +74,8 @@ export const ScaffolderPageContents = ({ }, }; + const { allowed } = usePermission(catalogEntityCreatePermission); + return (
- + {allowed && ( + + )} Create new software components using standard templates. Different templates create different kinds of components (services, websites, diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 8ce7ecf6b3..852ae3d81c 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -17,12 +17,13 @@ import { JsonObject, JsonValue } from '@backstage/types'; import { LinearProgress } from '@material-ui/core'; import { FormValidation, IChangeEvent } from '@rjsf/core'; import qs from 'qs'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useContext, useState } from 'react'; import { generatePath, Navigate, useNavigate } from 'react-router'; import { useParams } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; import { scaffolderApiRef } from '../../api'; import { CustomFieldValidator, FieldExtensionOptions } from '../../extensions'; +import { SecretsContext } from '../secrets/SecretsContext'; import { rootRouteRef } from '../../routes'; import { MultistepJsonForm } from '../MultistepJsonForm'; @@ -115,6 +116,7 @@ export const TemplatePage = ({ customFieldExtensions?: FieldExtensionOptions[]; }) => { const apiHolder = useApiHolder(); + const secretsContext = useContext(SecretsContext); const errorApi = useApi(errorApiRef); const scaffolderApi = useApi(scaffolderApiRef); const { templateName } = useParams(); @@ -135,7 +137,11 @@ export const TemplatePage = ({ ); const handleCreate = async () => { - const id = await scaffolderApi.scaffold(templateName, formState); + const id = await scaffolderApi.scaffold( + templateName, + formState, + secretsContext?.secrets, + ); const formParams = qs.stringify( { formData: formState }, diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx new file mode 100644 index 0000000000..829495cdda --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -0,0 +1,177 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useContext } from 'react'; +import { RepoUrlPicker } from './RepoUrlPicker'; +import Form from '@rjsf/core'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { + scmIntegrationsApiRef, + ScmIntegrationsApi, + scmAuthApiRef, + ScmAuthApi, +} from '@backstage/integration-react'; +import { scaffolderApiRef, ScaffolderApi } from '../../../api'; +import { + SecretsContextProvider, + SecretsContext, +} from '../../secrets/SecretsContext'; +import { act, fireEvent } from '@testing-library/react'; + +describe('RepoUrlPicker', () => { + const mockScaffolderApi: Partial = { + getIntegrationsList: async () => [ + { host: 'github.com', type: 'github', title: 'github.com' }, + { host: 'dev.azure.com', type: 'azure', title: 'dev.azure.com' }, + ], + }; + + const mockIntegrationsApi: Partial = { + byHost: () => ({ type: 'github' }), + }; + + const mockScmAuthApi: Partial = { + getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }), + }; + + describe('happy path rendering', () => { + it('should render the repo url picker with minimal props', async () => { + const onSubmit = jest.fn(); + const { getAllByRole, getByRole } = await renderInTestApp( + + +
+ + , + ); + + const [ownerInput, repoInput] = getAllByRole('textbox'); + const submitButton = getByRole('button'); + + fireEvent.change(ownerInput, { target: { value: 'backstage' } }); + fireEvent.change(repoInput, { target: { value: 'repo123' } }); + + fireEvent.click(submitButton); + + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + formData: 'github.com?owner=backstage&repo=repo123', + }), + expect.anything(), + ); + }); + + it('should render properly with allowedHosts', async () => { + const { getByRole } = await renderInTestApp( + + + + + , + ); + + expect( + getByRole('option', { name: 'dev.azure.com' }), + ).toBeInTheDocument(); + }); + }); + + describe('requestUserCredentials', () => { + it('should call the scmAuthApi with the correct params', async () => { + const SecretsComponent = () => { + const value = useContext(SecretsContext); + return
{JSON.stringify(value)}
; + }; + const { getAllByRole, getByTestId } = await renderInTestApp( + + + + + + , + ); + + const [ownerInput, repoInput] = getAllByRole('textbox'); + + await act(async () => { + fireEvent.change(ownerInput, { target: { value: 'backstage' } }); + fireEvent.change(repoInput, { target: { value: 'repo123' } }); + + // need to wait for the debounce to finish + await new Promise(resolve => setTimeout(resolve, 600)); + }); + + expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({ + url: 'https://github.com/backstage/repo123', + additionalScope: { + repoWrite: true, + customScopes: { + github: ['workflow:write'], + }, + }, + }); + + const currentSecrets = JSON.parse( + getByTestId('current-secrets').textContent!, + ); + + expect(currentSecrets).toEqual({ + secrets: { testKey: 'abc123' }, + }); + }); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 998fa41588..1ccaed4e86 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -14,7 +14,10 @@ * limitations under the License. */ import { useApi } from '@backstage/core-plugin-api'; -import { scmIntegrationsApiRef } from '@backstage/integration-react'; +import { + scmIntegrationsApiRef, + scmAuthApiRef, +} from '@backstage/integration-react'; import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { GithubRepoPicker } from './GithubRepoPicker'; import { GitlabRepoPicker } from './GitlabRepoPicker'; @@ -24,10 +27,21 @@ import { FieldExtensionComponentProps } from '../../../extensions'; import { RepoUrlPickerHost } from './RepoUrlPickerHost'; import { parseRepoPickerUrl, serializeRepoPickerUrl } from './utils'; import { RepoUrlPickerState } from './types'; +import useDebounce from 'react-use/lib/useDebounce'; +import { useTemplateSecrets } from '../../secrets'; export interface RepoUrlPickerUiOptions { allowedHosts?: string[]; allowedOwners?: string[]; + requestUserCredentials?: { + secretsKey: string; + additionalScopes?: { + github?: string[]; + gitlab?: string[]; + bitbucket?: string[]; + azure?: string[]; + }; + }; } export const RepoUrlPicker = ( @@ -38,7 +52,8 @@ export const RepoUrlPicker = ( parseRepoPickerUrl(formData), ); const integrationApi = useApi(scmIntegrationsApiRef); - + const scmAuthApi = useApi(scmAuthApiRef); + const { setSecret } = useTemplateSecrets(); const allowedHosts = useMemo( () => uiSchema?.['ui:options']?.allowedHosts ?? [], [uiSchema], @@ -66,6 +81,42 @@ export const RepoUrlPicker = ( [setState], ); + useDebounce( + async () => { + const { requestUserCredentials } = uiSchema?.['ui:options'] ?? {}; + + if ( + !requestUserCredentials || + !(state.host && state.owner && state.repoName) + ) { + return; + } + + const [host, owner, repoName] = [ + state.host, + state.owner, + state.repoName, + ].map(encodeURIComponent); + + // user has requested that we use the users credentials + // so lets grab them using the scmAuthApi and pass through + // any additional scopes from the ui:options + const { token } = await scmAuthApi.getCredentials({ + url: `https://${host}/${owner}/${repoName}`, + additionalScope: { + repoWrite: true, + customScopes: requestUserCredentials.additionalScopes, + }, + }); + + // set the secret using the key provided in the the ui:options for use + // in the templating the manifest with ${{ secrets[secretsKey] }} + setSecret({ [requestUserCredentials.secretsKey]: token }); + }, + 500, + [state, uiSchema], + ); + const hostType = (state.host && integrationApi.byHost(state.host)?.type) ?? null; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx index 261429c757..ec09494a1b 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx @@ -37,16 +37,23 @@ export const RepoUrlPickerHost = (props: { }); useEffect(() => { - if (hosts && !host) { - // This is only hear to set the default as the first one in the hosts array - // if the host is not set yet and there is a list of hosts. - onChange(hosts[0]); + // If there is no host chosen currently + if (!host) { + // Set the first of the allowedHosts option if that available + if (hosts?.length) { + onChange(hosts[0]); + // if there's no hosts provided, fallback to using the first integration + } else if (integrations?.length) { + onChange(integrations[0].host); + } } - }, [hosts, host, onChange]); + }, [hosts, host, onChange, integrations]); + // If there are no allowedHosts provided, then show all integrations. Otherwise, only show integrations + // that are provided in the dropdown for the user to choose from. const hostsOptions: SelectItem[] = integrations ? integrations - .filter(i => hosts?.includes(i.host)) + .filter(i => (hosts?.length ? hosts?.includes(i.host) : true)) .map(i => ({ label: i.title, value: i.host })) : [{ label: 'Loading...', value: 'loading' }]; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/utils.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/utils.ts index d1f2302d3e..2f17f0aef7 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/utils.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/utils.ts @@ -44,22 +44,22 @@ export function serializeRepoPickerUrl(data: RepoUrlPickerState) { export function parseRepoPickerUrl( url: string | undefined, ): RepoUrlPickerState { - let host = undefined; - let owner = undefined; - let repoName = undefined; - let organization = undefined; - let workspace = undefined; - let project = undefined; + let host = ''; + let owner = ''; + let repoName = ''; + let organization = ''; + let workspace = ''; + let project = ''; try { if (url) { const parsed = new URL(`https://${url}`); host = parsed.host; - owner = parsed.searchParams.get('owner') || undefined; - repoName = parsed.searchParams.get('repo') || undefined; - organization = parsed.searchParams.get('organization') || undefined; - workspace = parsed.searchParams.get('workspace') || undefined; - project = parsed.searchParams.get('project') || undefined; + owner = parsed.searchParams.get('owner') || ''; + repoName = parsed.searchParams.get('repo') || ''; + organization = parsed.searchParams.get('organization') || ''; + workspace = parsed.searchParams.get('workspace') || ''; + project = parsed.searchParams.get('project') || ''; } } catch { /* ok */ diff --git a/plugins/scaffolder/src/components/secrets/SecretsContext.test.tsx b/plugins/scaffolder/src/components/secrets/SecretsContext.test.tsx new file mode 100644 index 0000000000..37d35c9015 --- /dev/null +++ b/plugins/scaffolder/src/components/secrets/SecretsContext.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useContext } from 'react'; +import { + useTemplateSecrets, + SecretsContextProvider, + SecretsContext, +} from './SecretsContext'; +import { renderHook, act } from '@testing-library/react-hooks'; + +describe('SecretsContext', () => { + it('should allow the setting of secrets in the context', async () => { + const { result } = renderHook( + () => ({ + hook: useTemplateSecrets(), + context: useContext(SecretsContext), + }), + { + wrapper: ({ children }) => ( + {children} + ), + }, + ); + expect(result.current.context?.secrets.foo).toEqual(undefined); + + act(() => result.current.hook.setSecret({ foo: 'bar' })); + + expect(result.current.context?.secrets.foo).toEqual('bar'); + }); +}); diff --git a/plugins/scaffolder/src/components/secrets/SecretsContext.tsx b/plugins/scaffolder/src/components/secrets/SecretsContext.tsx new file mode 100644 index 0000000000..6570f9d901 --- /dev/null +++ b/plugins/scaffolder/src/components/secrets/SecretsContext.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { + useState, + useCallback, + useContext, + createContext, + PropsWithChildren, +} from 'react'; + +type SecretsContextContents = { + secrets: Record; + setSecrets: React.Dispatch>>; +}; + +/** + * The actual context object. + */ +export const SecretsContext = createContext( + undefined, +); + +/** + * The Context Provider that holds the state for the secrets. + * + * @public + */ +export const SecretsContextProvider = ({ children }: PropsWithChildren<{}>) => { + const [secrets, setSecrets] = useState>({}); + + return ( + + {children} + + ); +}; + +/** + * Hook to access the secrets context. + * @public + */ +export const useTemplateSecrets = () => { + const value = useContext(SecretsContext); + if (!value) { + throw new Error( + 'useTemplateSecrets must be used within a SecretsContextProvider', + ); + } + + const { setSecrets } = value; + + const setSecret = useCallback( + (input: Record) => { + setSecrets(currentSecrets => ({ ...currentSecrets, ...input })); + }, + [setSecrets], + ); + + return { setSecret }; +}; diff --git a/plugins/scaffolder/src/components/secrets/index.ts b/plugins/scaffolder/src/components/secrets/index.ts new file mode 100644 index 0000000000..44adb2a00a --- /dev/null +++ b/plugins/scaffolder/src/components/secrets/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { useTemplateSecrets } from './SecretsContext'; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 8012f14f32..adb2aab035 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -56,5 +56,6 @@ export { FavouriteTemplate } from './components/FavouriteTemplate'; export { TemplateList } from './components/TemplateList'; export type { TemplateListProps } from './components/TemplateList'; export { TemplateTypePicker } from './components/TemplateTypePicker'; +export * from './components/secrets'; export { TaskPage } from './components/TaskPage'; export type { TaskPageProps } from './components/TaskPage'; diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 42944fba0d..79408a2e69 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -30,8 +30,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-common": "^0.10.5", - "@backstage/cli": "^0.13.0", + "@backstage/backend-common": "^0.10.6-next.0", + "@backstage/cli": "^0.13.1-next.1", "@elastic/elasticsearch-mock": "^0.3.0" }, "files": [ diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 8abeaa10e8..245a4ded3f 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-search-backend-module-pg +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 2b7b9bbc77..51a7933ae3 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-pg", "description": "A module for the search backend that implements search using PostgreSQL", - "version": "0.2.4", + "version": "0.2.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,15 +20,15 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/search-common": "^0.2.2", "@backstage/plugin-search-backend-node": "^0.4.5", "lodash": "^4.17.21", "knex": "^0.95.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.15", - "@backstage/cli": "^0.13.0" + "@backstage/backend-test-utils": "^0.1.16", + "@backstage/cli": "^0.13.1" }, "files": [ "dist", diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 7610c054c5..fb72994de5 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -26,8 +26,8 @@ "@types/lunr": "^2.3.3" }, "devDependencies": { - "@backstage/backend-common": "^0.10.5", - "@backstage/cli": "^0.13.0" + "@backstage/backend-common": "^0.10.6-next.0", + "@backstage/cli": "^0.13.1-next.1" }, "files": [ "dist" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index ed7b0811ad..46daea6df6 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-search-backend +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0 + - @backstage/backend-common@0.10.6 + - @backstage/plugin-permission-node@0.4.2 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.1 + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-permission-node@0.4.2-next.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.0 + - @backstage/plugin-permission-node@0.4.2-next.0 + ## 0.4.0 ### Minor Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index d3292e88bd..3329115650 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend", "description": "The Backstage backend plugin that provides your backstage app with search", - "version": "0.4.0", + "version": "0.4.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,13 +20,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", "@backstage/search-common": "^0.2.2", - "@backstage/plugin-auth-backend": "^0.8.0", + "@backstage/plugin-auth-backend": "^0.9.0", "@backstage/plugin-permission-common": "^0.4.0-next.0", - "@backstage/plugin-permission-node": "^0.4.1", + "@backstage/plugin-permission-node": "^0.4.2", "@backstage/plugin-search-backend-node": "^0.4.5", "@backstage/types": "^0.1.1", "@types/express": "^4.17.6", @@ -40,7 +40,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 412edde26c..f6692413b4 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search +## 0.6.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.6.0 ### Minor Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 478b49d072..6536c9c386 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search", "description": "The Backstage plugin that provides your backstage app with search", - "version": "0.6.0", + "version": "0.6.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ "dependencies": { "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/search-common": "^0.2.2", "@backstage/theme": "^0.2.14", "@backstage/types": "^0.1.1", @@ -52,10 +52,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 6e09dbf91a..acccd2dc4c 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-sentry +## 0.3.35 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.3.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.3.34 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index bcfaa33d41..274cc6e1e2 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry", "description": "A Backstage plugin that integrates towards Sentry", - "version": "0.3.34", + "version": "0.3.35", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,9 +33,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index e9388c293e..60c0b9fe84 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-shortcuts +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.1.20 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index a830943dbc..23e61cda42 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-shortcuts", "description": "A Backstage plugin that provides a shortcuts feature to the sidebar", - "version": "0.1.20", + "version": "0.1.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/theme": "^0.2.14", "@backstage/types": "^0.1.1", @@ -39,10 +39,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index d3b5ce4f2f..7cbabeda41 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-sonarqube +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.2.14 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 656430ce69..4d6d83a67a 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sonarqube", "description": "", - "version": "0.2.14", + "version": "0.2.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,9 +34,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -50,10 +50,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index b5e77459bc..90ff0a5bd1 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-splunk-on-call +## 0.3.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.3.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.3.20 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index ada127bbfc..f3de0d3602 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-splunk-on-call", "description": "A Backstage plugin that integrates towards Splunk On-Call", - "version": "0.3.20", + "version": "0.3.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md index 9346b5c402..d136927b9b 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-tech-insights-backend-module-jsonfc +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + - @backstage/plugin-tech-insights-node@0.2.1 + +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-tech-insights-node@0.2.1-next.0 + ## 0.1.6 ### Patch Changes diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 66f13b7d1f..77ce4b3268 100644 --- a/plugins/tech-insights-backend-module-jsonfc/package.json +++ b/plugins/tech-insights-backend-module-jsonfc/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend-module-jsonfc", - "version": "0.1.6", + "version": "0.1.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", "@backstage/plugin-tech-insights-common": "^0.2.1", - "@backstage/plugin-tech-insights-node": "^0.2.0", + "@backstage/plugin-tech-insights-node": "^0.2.1", "ajv": "^7.0.3", "json-rules-engine": "^6.1.2", "lodash": "^4.17.21", @@ -43,7 +43,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/node-cron": "^3.0.1" }, "files": [ diff --git a/plugins/tech-insights-backend/CHANGELOG.md b/plugins/tech-insights-backend/CHANGELOG.md index 7cf31f5b61..a9032f9c98 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-tech-insights-backend +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + - @backstage/plugin-tech-insights-node@0.2.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + - @backstage/plugin-tech-insights-node@0.2.1-next.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index febaf37dac..d63bb60499 100644 --- a/plugins/tech-insights-backend/package.json +++ b/plugins/tech-insights-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,13 +31,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", "@backstage/plugin-tech-insights-common": "^0.2.1", - "@backstage/plugin-tech-insights-node": "^0.2.0", + "@backstage/plugin-tech-insights-node": "^0.2.1", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -51,8 +51,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.15", - "@backstage/cli": "^0.13.0", + "@backstage/backend-test-utils": "^0.1.16", + "@backstage/cli": "^0.13.1", "@types/supertest": "^2.0.8", "@types/node-cron": "^3.0.0", "@types/semver": "^7.3.8", diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json index 48ccb0d1a1..2d9f166b09 100644 --- a/plugins/tech-insights-common/package.json +++ b/plugins/tech-insights-common/package.json @@ -35,7 +35,7 @@ "@backstage/types": "^0.1.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0" + "@backstage/cli": "^0.13.1-next.0" }, "files": [ "dist" diff --git a/plugins/tech-insights-node/CHANGELOG.md b/plugins/tech-insights-node/CHANGELOG.md index 502cfdaa9b..4f3ed0d13e 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-tech-insights-node +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json index 6bca50cd0a..7ed74ab76d 100644 --- a/plugins/tech-insights-node/package.json +++ b/plugins/tech-insights-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-node", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/config": "^0.1.13", "@backstage/plugin-tech-insights-common": "^0.2.1", "@types/luxon": "^2.0.5", @@ -38,7 +38,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0" + "@backstage/cli": "^0.13.1" }, "files": [ "dist" diff --git a/plugins/tech-insights/CHANGELOG.md b/plugins/tech-insights/CHANGELOG.md index c760b0c5f5..a63a1a8f80 100644 --- a/plugins/tech-insights/CHANGELOG.md +++ b/plugins/tech-insights/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-tech-insights +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.1.6 ### Patch Changes diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index 1d4266777f..2f0d2f3638 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights", - "version": "0.1.6", + "version": "0.1.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/plugin-tech-insights-common": "^0.2.1", "@backstage/theme": "^0.2.14", "@backstage/types": "^0.1.1", @@ -39,10 +39,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 5648323b33..b7952b6f12 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-tech-radar +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.5.3 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index cf612f7dce..4da9252c6a 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-tech-radar", "description": "A Backstage plugin that lets you display a Tech Radar for your organization", - "version": "0.5.3", + "version": "0.5.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", @@ -46,10 +46,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 246d612833..4593b8db5b 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-techdocs-backend +## 0.13.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@0.1.2 + - @backstage/backend-common@0.10.6 + - @backstage/techdocs-common@0.11.6 + +## 0.13.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@0.1.2-next.0 + - @backstage/backend-common@0.10.6-next.0 + - @backstage/techdocs-common@0.11.6-next.0 + ## 0.13.1 ### Patch Changes diff --git a/plugins/techdocs-backend/examples/documented-component/docs/extensions.md b/plugins/techdocs-backend/examples/documented-component/docs/extensions.md index e9337ce042..0fda26302d 100644 --- a/plugins/techdocs-backend/examples/documented-component/docs/extensions.md +++ b/plugins/techdocs-backend/examples/documented-component/docs/extensions.md @@ -88,6 +88,12 @@ Weather: :sunny: :umbrella: :cloud: :snowflake: Animals: :tiger: :horse: :turtle: :wolf: :frog: +### Attributes + +[A Download Link](./images/backstage-logo-cncf.svg){: download } + +![A Scaled Image](./images/backstage-logo-cncf.svg){: style="width: 100px" } + ### MDX truly sane lists - attributes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index c760783243..292b70a61d 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-backend", "description": "The Backstage backend plugin that renders technical documentation for your components", - "version": "0.13.1", + "version": "0.13.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,15 +31,15 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", - "@backstage/plugin-catalog-common": "^0.1.1-next.0", + "@backstage/plugin-catalog-common": "^0.1.2", "@backstage/search-common": "^0.2.2", - "@backstage/techdocs-common": "^0.11.5", + "@backstage/techdocs-common": "^0.11.6", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", "dockerode": "^3.3.1", @@ -53,8 +53,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/test-utils": "^0.2.4", "@types/dockerode": "^3.3.0", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 2796548251..e0547de64e 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-techdocs +## 0.13.2 + +### Patch Changes + +- 742434a6ba: Fixed a bug where links to files within a TechDocs site that use the `download` attribute would result in a 404 in cases where the TechDocs backend and Backstage frontend application are on the same host. +- 359c31e31d: Added support for documentation using the raw `` tag to point to relative resources like audio or video files. +- 18317a08db: Fixed a bug where copy-to-clipboard buttons were appended to unintended elements. +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + - @backstage/integration-react@0.1.20 + - @backstage/plugin-catalog@0.7.11 + - @backstage/plugin-search@0.6.1 + +## 0.13.2-next.1 + +### Patch Changes + +- 742434a6ba: Fixed a bug where links to files within a TechDocs site that use the `download` attribute would result in a 404 in cases where the TechDocs backend and Backstage frontend application are on the same host. +- Updated dependencies + - @backstage/core-components@0.8.7-next.1 + - @backstage/plugin-catalog-react@0.6.13-next.1 + - @backstage/plugin-catalog@0.7.11-next.1 + +## 0.13.2-next.0 + +### Patch Changes + +- 359c31e31d: Added support for documentation using the raw `` tag to point to relative resources like audio or video files. +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/integration-react@0.1.20-next.0 + - @backstage/plugin-catalog@0.7.11-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + - @backstage/plugin-search@0.6.1-next.0 + ## 0.13.1 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 783ad7a5df..6e571ec5ac 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs", "description": "The Backstage plugin that renders technical documentation for your components", - "version": "0.13.1", + "version": "0.13.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,14 +34,14 @@ "dependencies": { "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", - "@backstage/integration-react": "^0.1.19", - "@backstage/plugin-catalog": "^0.7.10", - "@backstage/plugin-catalog-react": "^0.6.12", - "@backstage/plugin-search": "^0.6.0", + "@backstage/integration-react": "^0.1.20", + "@backstage/plugin-catalog": "^0.7.11", + "@backstage/plugin-catalog-react": "^0.6.13", + "@backstage/plugin-search": "^0.6.1", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -62,10 +62,10 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts index 11e8375420..0fb72caab3 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts @@ -42,6 +42,10 @@ const fixture = ` Download Now + `; @@ -88,12 +92,18 @@ describe('addBaseUrl', () => { ); expect(techdocsStorageApi.getBaseUrl).toHaveBeenNthCalledWith( 3, - 'astyle.css', + 'avideo.mp4', mockEntityId, '', ); expect(techdocsStorageApi.getBaseUrl).toHaveBeenNthCalledWith( 4, + 'astyle.css', + mockEntityId, + '', + ); + expect(techdocsStorageApi.getBaseUrl).toHaveBeenNthCalledWith( + 5, 'afile.pdf', mockEntityId, '', diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts index 8f08ec83a6..f3906b7b91 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts @@ -84,6 +84,7 @@ export const addBaseUrl = ({ await Promise.all([ updateDom(dom.querySelectorAll('img'), 'src'), updateDom(dom.querySelectorAll('script'), 'src'), + updateDom(dom.querySelectorAll('source'), 'src'), updateDom(dom.querySelectorAll('link'), 'href'), updateDom(dom.querySelectorAll('a[download]'), 'href'), ]); diff --git a/plugins/techdocs/src/reader/transformers/addLinkClickListener.test.ts b/plugins/techdocs/src/reader/transformers/addLinkClickListener.test.ts index fe3a6e557c..f079750d8a 100644 --- a/plugins/techdocs/src/reader/transformers/addLinkClickListener.test.ts +++ b/plugins/techdocs/src/reader/transformers/addLinkClickListener.test.ts @@ -71,4 +71,31 @@ describe('addLinkClickListener', () => { expect(fn).toHaveBeenCalledTimes(0); }); + + it('does not call onClick when a link has a download attribute', async () => { + const fn = jest.fn(); + const shadowDom = await createTestShadowDom( + ` + + + + Download + + + `, + { + preTransformers: [], + postTransformers: [ + addLinkClickListener({ + baseUrl: 'http://localhost:3000', + onClick: fn, + }), + ], + }, + ); + + shadowDom.querySelector('a')?.click(); + + expect(fn).toHaveBeenCalledTimes(0); + }); }); diff --git a/plugins/techdocs/src/reader/transformers/addLinkClickListener.ts b/plugins/techdocs/src/reader/transformers/addLinkClickListener.ts index f02d88793b..3d0bd137c0 100644 --- a/plugins/techdocs/src/reader/transformers/addLinkClickListener.ts +++ b/plugins/techdocs/src/reader/transformers/addLinkClickListener.ts @@ -32,7 +32,7 @@ export const addLinkClickListener = ({ const href = target.getAttribute('href'); if (!href) return; - if (href.startsWith(baseUrl)) { + if (href.startsWith(baseUrl) && !elem.hasAttribute('download')) { e.preventDefault(); onClick(e, href); } diff --git a/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts b/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts index 679121d9bd..daa074f6e4 100644 --- a/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts +++ b/plugins/techdocs/src/reader/transformers/copyToClipboard.test.ts @@ -32,7 +32,7 @@ describe('copyToClipboard', () => { - ${expectedClipboard} +
${expectedClipboard}
`, @@ -46,4 +46,25 @@ describe('copyToClipboard', () => { expect(clipboardSpy).toHaveBeenCalledWith(expectedClipboard); }); + + it('only gets applied to code blocks', async () => { + const expectedClipboard = 'function foo() {return "bar";}'; + const shadowDom = await createTestShadowDom( + ` + + + + ${expectedClipboard} + + + `, + { + preTransformers: [], + postTransformers: [copyToClipboard()], + }, + ); + + const copyButton = shadowDom.querySelector('button'); + expect(copyButton).toBe(null); + }); }); diff --git a/plugins/techdocs/src/reader/transformers/copyToClipboard.ts b/plugins/techdocs/src/reader/transformers/copyToClipboard.ts index 24b46c0b85..d0569c0fd9 100644 --- a/plugins/techdocs/src/reader/transformers/copyToClipboard.ts +++ b/plugins/techdocs/src/reader/transformers/copyToClipboard.ts @@ -22,7 +22,7 @@ import type { Transformer } from './transformer'; */ export const copyToClipboard = (): Transformer => { return dom => { - Array.from(dom.querySelectorAll('code')).forEach(codeElem => { + Array.from(dom.querySelectorAll('pre > code')).forEach(codeElem => { const button = document.createElement('button'); const toBeCopied = codeElem.textContent || ''; button.className = 'md-clipboard md-icon'; diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index ddbc5d215a..9e2275dc7b 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-todo-backend +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6 + +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.6-next.0 + ## 0.1.19 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 3a8eb1dcac..456600e0be 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo-backend", "description": "A Backstage backend plugin that lets you browse TODO comments in your source code", - "version": "0.1.19", + "version": "0.1.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,7 +25,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.5", + "@backstage/backend-common": "^0.10.6", "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/config": "^0.1.13", @@ -39,7 +39,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", + "@backstage/cli": "^0.13.1", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md index d3679e2fd9..e659da3131 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-todo +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + - @backstage/plugin-catalog-react@0.6.13 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + - @backstage/plugin-catalog-react@0.6.13-next.0 + ## 0.1.20 ### Patch Changes diff --git a/plugins/todo/package.json b/plugins/todo/package.json index c2fa299175..fc9893fcd9 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo", "description": "A Backstage plugin that lets you browse TODO comments in your source code", - "version": "0.1.20", + "version": "0.1.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,10 +28,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.12", + "@backstage/plugin-catalog-react": "^0.6.13", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 091a12c728..b4438be6ce 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-user-settings +## 0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.3.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.3.17 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 94987cf96d..4506cdfb75 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings", "description": "A Backstage plugin that provides a settings page", - "version": "0.3.17", + "version": "0.3.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", @@ -44,10 +44,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/xcmetrics/CHANGELOG.md b/plugins/xcmetrics/CHANGELOG.md index 632947f505..652dcca6b9 100644 --- a/plugins/xcmetrics/CHANGELOG.md +++ b/plugins/xcmetrics/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-xcmetrics +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.7-next.0 + ## 0.2.16 ### Patch Changes diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index bd994dfc28..7680c787d5 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-xcmetrics", "description": "A Backstage plugin that shows XCode build metrics for your components", - "version": "0.2.16", + "version": "0.2.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.8.6", + "@backstage/core-components": "^0.8.7", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", "@backstage/theme": "^0.2.14", @@ -37,10 +37,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.0", - "@backstage/core-app-api": "^0.5.1", - "@backstage/dev-utils": "^0.2.19", - "@backstage/test-utils": "^0.2.3", + "@backstage/cli": "^0.13.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.20", + "@backstage/test-utils": "^0.2.4", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/scripts/api-extractor.ts b/scripts/api-extractor.ts index bfb8b525fc..dfbd91f76b 100644 --- a/scripts/api-extractor.ts +++ b/scripts/api-extractor.ts @@ -193,7 +193,7 @@ const SKIPPED_PACKAGES = [ join('packages', 'codemods'), join('packages', 'create-app'), join('packages', 'e2e-test'), - join('packages', 'embedded-techdocs-app'), + join('packages', 'techdocs-cli-embedded-app'), join('packages', 'storybook'), join('packages', 'techdocs-cli'), ]; diff --git a/scripts/create-github-release.js b/scripts/create-github-release.js index 6150b5f4bd..f25c6838fb 100755 --- a/scripts/create-github-release.js +++ b/scripts/create-github-release.js @@ -142,7 +142,8 @@ async function getReleaseDescriptionFromCommitMessage(commitMessage) { // Use the PR description to prepare for the release description const isChangesetRelease = commitMessage.includes(CHANGESET_RELEASE_BRANCH); if (isChangesetRelease) { - return data.body.split('\n').slice(3).join('\n'); + const lines = data.body.split('\n'); + return lines.slice(lines.indexOf('# Releases') + 1).join('\n'); } return data.body; @@ -182,7 +183,6 @@ async function main() { const releaseDescription = await getReleaseDescriptionFromCommitMessage( commitMessage, ); - await createRelease(releaseDescription); } diff --git a/scripts/create-release-tag.js b/scripts/create-release-tag.js index a71afe513b..042b00866a 100755 --- a/scripts/create-release-tag.js +++ b/scripts/create-release-tag.js @@ -17,6 +17,8 @@ */ const { Octokit } = require('@octokit/rest'); +const path = require('path'); +const fs = require('fs-extra'); const baseOptions = { owner: 'backstage', diff --git a/scripts/prepare-release.js b/scripts/prepare-release.js index bfae8e661c..8025022cf8 100755 --- a/scripts/prepare-release.js +++ b/scripts/prepare-release.js @@ -62,14 +62,22 @@ async function updatePatchVersions() { throw new Error(`Invalid base version ${version} for package ${name}`); } - const currentVersion = semver.parse(pkg.packageJson.version); - const targetVersion = semver.parse(version).inc('patch'); - targetVersion.prerelease = currentVersion.prerelease; + let targetVersion = version; + + // If we're currently in a pre-release we need to manually execute the + // patch bump up to the next version. And we also need to make sure we + // resume the releases at the same pre-release tag. + const currentPrerelease = semver.prerelease(pkg.packageJson.version); + if (currentPrerelease) { + const parsed = targetVersion.parse(version); + parsed.inc('patch'); + parsed.prerelease = currentPrerelease; + targetVersion = parsed.format(); + } - const targetVersionString = targetVersion.format(); pendingVersionBumps.set(name, { - targetVersion: targetVersionString, - targetRange: `^${targetVersionString}`, + targetVersion, + targetRange: `^${targetVersion}`, }); } diff --git a/scripts/verify-changesets.js b/scripts/verify-changesets.js index 8d828ee058..9e3064eeac 100755 --- a/scripts/verify-changesets.js +++ b/scripts/verify-changesets.js @@ -26,7 +26,7 @@ const privatePackages = new Set([ 'example-backend', 'e2e-test', 'storybook', - 'embedded-techdocs-app', + 'techdocs-cli-embedded-app', ]); async function main() { diff --git a/storybook/package.json b/storybook/package.json index 02e52fb937..9f10b9a972 100644 --- a/storybook/package.json +++ b/storybook/package.json @@ -15,12 +15,12 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "@storybook/addon-a11y": "^6.4.17", - "@storybook/addon-actions": "^6.4.14", - "@storybook/addon-links": "^6.4.17", + "@storybook/addon-a11y": "^6.4.18", + "@storybook/addon-actions": "^6.4.18", + "@storybook/addon-links": "^6.4.18", "@storybook/addon-storysource": "^6.4.17", "@storybook/addons": "^6.4.14", - "@storybook/react": "^6.4.17", + "@storybook/react": "^6.4.18", "storybook-dark-mode": "^1.0.8" }, "peerDependencies": { diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 5b15ee9adf..d25fdeeead 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -1310,19 +1310,19 @@ resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9" integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA== -"@storybook/addon-a11y@^6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.4.17.tgz#b7efe7f7a52c81392e06efafc34044db784fd3ce" - integrity sha512-uIgl9vJwY4//7i+JEu47Lgi1wOGOskHQ0+H/S8DPGcEMF2xqK/w3BjgSEWa8NPYfYyxkf/yHvCIsa99b/3phUg== +"@storybook/addon-a11y@^6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.4.18.tgz#c61f71cd7b17aff408835ee35e29bf4a0716ece7" + integrity sha512-sqsA5pXXKKDsquSXu5KC8WxQ1gg5ZoNIltWRgmJCEt4a0bkGUzn2iz+uW/gbt4NOVWGPXKvmMBLT/q4Q9gb+og== dependencies: - "@storybook/addons" "6.4.17" - "@storybook/api" "6.4.17" - "@storybook/channels" "6.4.17" - "@storybook/client-logger" "6.4.17" - "@storybook/components" "6.4.17" - "@storybook/core-events" "6.4.17" + "@storybook/addons" "6.4.18" + "@storybook/api" "6.4.18" + "@storybook/channels" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/components" "6.4.18" + "@storybook/core-events" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/theming" "6.4.17" + "@storybook/theming" "6.4.18" axe-core "^4.2.0" core-js "^3.8.2" global "^4.4.0" @@ -1332,17 +1332,17 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/addon-actions@^6.4.14": - version "6.4.14" - resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.4.14.tgz#be5590438943487b4243b2fe16619557cb9ca0ce" - integrity sha512-EBraATDCKCbb1IpT+bTIV+noFIoK5ykXj8Nt0qmQGD2OC1cZovIyH3DigyD0/3D55znGzxqRruTK8lm0nc1jbg== +"@storybook/addon-actions@^6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.4.18.tgz#e997060e1b0af62f9f831301a56a3addfc1f1365" + integrity sha512-qPw5qfbWPmyOdaXxAVAbdVLVVE31gRrkH0ESUps+FXVNypRz1/0lJ6M2VrtOHMrFbGBl94SALdqsHOx6OYZKwg== dependencies: - "@storybook/addons" "6.4.14" - "@storybook/api" "6.4.14" - "@storybook/components" "6.4.14" - "@storybook/core-events" "6.4.14" + "@storybook/addons" "6.4.18" + "@storybook/api" "6.4.18" + "@storybook/components" "6.4.18" + "@storybook/core-events" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/theming" "6.4.14" + "@storybook/theming" "6.4.18" core-js "^3.8.2" fast-deep-equal "^3.1.3" global "^4.4.0" @@ -1356,16 +1356,16 @@ util-deprecate "^1.0.2" uuid-browser "^3.1.0" -"@storybook/addon-links@^6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-6.4.17.tgz#83675642669c239c8d3ddaacd75348366e280426" - integrity sha512-ytGEe7sfOW10wwc0NIWSGtNgGM8ql8EOg7ZhrgXiRgQE0vD5NXRJi8FWzBdb06/G3cURYwveKR4Ea9mABZEaUw== +"@storybook/addon-links@^6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-6.4.18.tgz#edb61db6c291056f7d3c64566aea436a6796c50a" + integrity sha512-yIbL57+tV1Ei2b7zTGU/T7muBFByTPm/8IN5SA5tSFYRTR9VtFuvBXco6I9Wz9GLN/REyVa4+AoDahokk7+vPQ== dependencies: - "@storybook/addons" "6.4.17" - "@storybook/client-logger" "6.4.17" - "@storybook/core-events" "6.4.17" + "@storybook/addons" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/core-events" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/router" "6.4.17" + "@storybook/router" "6.4.18" "@types/qs" "^6.9.5" core-js "^3.8.2" global "^4.4.0" @@ -1394,24 +1394,7 @@ react-syntax-highlighter "^13.5.3" regenerator-runtime "^0.13.7" -"@storybook/addons@6.4.14": - version "6.4.14" - resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.4.14.tgz#45d6937bc2ece33ceadc5358b2a2298d2a0d1e95" - integrity sha512-Snu42ejLyBAh6PWdlrdI72HKN1oKY7q0R9qEID2wk953WrqgGu4URakp14YLxghJCyKTSfGPs6LNZRRI6H5xgA== - dependencies: - "@storybook/api" "6.4.14" - "@storybook/channels" "6.4.14" - "@storybook/client-logger" "6.4.14" - "@storybook/core-events" "6.4.14" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/router" "6.4.14" - "@storybook/theming" "6.4.14" - "@types/webpack-env" "^1.16.0" - core-js "^3.8.2" - global "^4.4.0" - regenerator-runtime "^0.13.7" - -"@storybook/addons@6.4.17", "@storybook/addons@^6.4.14": +"@storybook/addons@6.4.17": version "6.4.17" resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.4.17.tgz#d040db3ddcf72fd9e7df8b8fce2a6dc88578c87e" integrity sha512-C/hji0Bc7+tssGqaD0JYd/Pz0GM46xbRpdgHSVLInYdhJrb5a9IG6INCbcB8CXeReDKWJCLAaj2+z79Wa96bFQ== @@ -1428,28 +1411,22 @@ global "^4.4.0" regenerator-runtime "^0.13.7" -"@storybook/api@6.4.14": - version "6.4.14" - resolved "https://registry.npmjs.org/@storybook/api/-/api-6.4.14.tgz#a477646f7e020a362f044d2e614e3d1a86ba8f6f" - integrity sha512-GGGwB5+EquoausTXYx4dnLBBk2sOiS1Z58mDj0swBXCZdjfyUfLyxjxvvb/hl65ltufWP3IdmlKKaLiuARXNtw== +"@storybook/addons@6.4.18", "@storybook/addons@^6.4.14": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.4.18.tgz#fc92a4a608680f2e182a5e896ed382792f6b774e" + integrity sha512-fd3S79P4jJCYZNA2JxA1Xnkj0UlHGQ4Vg72aroWy4OQFlgGQor1LgPfM6RaJ9rh/4k4BXYPXsS7wzI0UWKG3Lw== dependencies: - "@storybook/channels" "6.4.14" - "@storybook/client-logger" "6.4.14" - "@storybook/core-events" "6.4.14" + "@storybook/api" "6.4.18" + "@storybook/channels" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/core-events" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/router" "6.4.14" - "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.4.14" + "@storybook/router" "6.4.18" + "@storybook/theming" "6.4.18" + "@types/webpack-env" "^1.16.0" core-js "^3.8.2" - fast-deep-equal "^3.1.3" global "^4.4.0" - lodash "^4.17.21" - memoizerific "^1.11.3" regenerator-runtime "^0.13.7" - store2 "^2.12.0" - telejson "^5.3.2" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" "@storybook/api@6.4.17": version "6.4.17" @@ -1474,10 +1451,33 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-webpack4@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/builder-webpack4/-/builder-webpack4-6.4.17.tgz#ad71aaa0a271941e2efe114d5bf7bc8feaa13dcf" - integrity sha512-jE1JehWj5gjLwafGuvV1OyBFVVhBCvv6ESc3QPm+jrsf4ZyB9xliTsnPt3bDggQhWpTEbxgGw7IkVc83ss4AOw== +"@storybook/api@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/api/-/api-6.4.18.tgz#92da2b69aeec712419bec9bab5c8434ff1776e97" + integrity sha512-tSbsHKklBysuSmw4T+cKzMj6mQh/42m9F8+2iJns2XG/IUKpMAzFg/9dlgCTW+ay6dJwsR79JGIc9ccIe4SMgQ== + dependencies: + "@storybook/channels" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/core-events" "6.4.18" + "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/router" "6.4.18" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.4.18" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + telejson "^5.3.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + +"@storybook/builder-webpack4@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/builder-webpack4/-/builder-webpack4-6.4.18.tgz#8bae72b9e982d35a5a9f2b7f9af9d85a9c2dc966" + integrity sha512-N/OGjTnc7CpVoDnfoI49uMjAIpGqh2lWHFYNIWaUoG1DNnTt1nCc49hw9awjFc5KgaYOwJmVg1SYYE8Afssu+Q== dependencies: "@babel/core" "^7.12.10" "@babel/plugin-proposal-class-properties" "^7.12.1" @@ -1500,22 +1500,22 @@ "@babel/preset-env" "^7.12.11" "@babel/preset-react" "^7.12.10" "@babel/preset-typescript" "^7.12.7" - "@storybook/addons" "6.4.17" - "@storybook/api" "6.4.17" - "@storybook/channel-postmessage" "6.4.17" - "@storybook/channels" "6.4.17" - "@storybook/client-api" "6.4.17" - "@storybook/client-logger" "6.4.17" - "@storybook/components" "6.4.17" - "@storybook/core-common" "6.4.17" - "@storybook/core-events" "6.4.17" - "@storybook/node-logger" "6.4.17" - "@storybook/preview-web" "6.4.17" - "@storybook/router" "6.4.17" + "@storybook/addons" "6.4.18" + "@storybook/api" "6.4.18" + "@storybook/channel-postmessage" "6.4.18" + "@storybook/channels" "6.4.18" + "@storybook/client-api" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/components" "6.4.18" + "@storybook/core-common" "6.4.18" + "@storybook/core-events" "6.4.18" + "@storybook/node-logger" "6.4.18" + "@storybook/preview-web" "6.4.18" + "@storybook/router" "6.4.18" "@storybook/semver" "^7.3.2" - "@storybook/store" "6.4.17" - "@storybook/theming" "6.4.17" - "@storybook/ui" "6.4.17" + "@storybook/store" "6.4.18" + "@storybook/theming" "6.4.18" + "@storybook/ui" "6.4.18" "@types/node" "^14.0.10" "@types/webpack" "^4.41.26" autoprefixer "^9.8.6" @@ -1549,39 +1549,30 @@ webpack-hot-middleware "^2.25.1" webpack-virtual-modules "^0.2.2" -"@storybook/channel-postmessage@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.4.17.tgz#9f439bee440479bfe8f86092701b3b63afc5195a" - integrity sha512-IaVkO/w7bn95Psm1iROlSsc/DHh9RiA7F151VLFD9VTh55qiIfeRssfBXIg3ueGUWm0K+Y9J1jQbcqJoEniMtw== +"@storybook/channel-postmessage@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.4.18.tgz#24547fe7cee599969fd62df22142ba7046099a8e" + integrity sha512-SKapUREPkqzKoBMpOJrZddE9PCR8CJkPTcDpjDqcRsTvToRWsux3pvzmuW4iGYnHNh+GQml7Rz9x85WfMIpfyQ== dependencies: - "@storybook/channels" "6.4.17" - "@storybook/client-logger" "6.4.17" - "@storybook/core-events" "6.4.17" + "@storybook/channels" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/core-events" "6.4.18" core-js "^3.8.2" global "^4.4.0" qs "^6.10.0" telejson "^5.3.2" -"@storybook/channel-websocket@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/channel-websocket/-/channel-websocket-6.4.17.tgz#780cc68bb3a31069196b35a232013764cc2320a3" - integrity sha512-HtApo/3upDvxSl6VU04F/JznMIltUHeyEqaQNlkqJbQ1VQEHky/M/XJZWT4I/b+nGMXCt0+z0P0ikZ6VZKzFsw== +"@storybook/channel-websocket@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/channel-websocket/-/channel-websocket-6.4.18.tgz#cf3a03e88b983c2953cb76a40a964806790567c4" + integrity sha512-ROqNZAFB1gP9u8dmlM4KxykXHsd1ifunBgFY3ncQKeRi2Oh30OMVB2ZhNdoIF8i8X5ZBwSpId1o6nQhL2e/EJA== dependencies: - "@storybook/channels" "6.4.17" - "@storybook/client-logger" "6.4.17" + "@storybook/channels" "6.4.18" + "@storybook/client-logger" "6.4.18" core-js "^3.8.2" global "^4.4.0" telejson "^5.3.2" -"@storybook/channels@6.4.14": - version "6.4.14" - resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.4.14.tgz#f7a5416c971febd26ed7b03a75d99fd819790e48" - integrity sha512-3QOVxFG6ZAxDXCta1ie4SUPQ3s50yHeuZzVg6uPp+DcC1FrXeDFYBcU9t0j/jrSgbeKcnFHWxmRHNy1BRyWv/A== - dependencies: - core-js "^3.8.2" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - "@storybook/channels@6.4.17": version "6.4.17" resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.4.17.tgz#95d05745a96b6059cea26d45aacca3967c401e26" @@ -1591,18 +1582,27 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/client-api@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.4.17.tgz#34476732eb4a698e7dcc774a21feca4529581889" - integrity sha512-qK8Bvsr2KzndAu8RxbBrieNUCltO/ynwtAohJ/29hAg/duf94CZjN0HkuTpQmd4lDip11d9o4Fz5UBWC0zMyOw== +"@storybook/channels@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.4.18.tgz#2907aca0039b5eb9ae305112f14c488c2621c2f6" + integrity sha512-Bh4l7VKKR2ImLbZ9XgL/DzT3lFv9+SLiCu1ozfpBZGHUCOLyHRnkG/h8wYvRkF9s3tpNwOtaCaqD1vkkZfr3uw== dependencies: - "@storybook/addons" "6.4.17" - "@storybook/channel-postmessage" "6.4.17" - "@storybook/channels" "6.4.17" - "@storybook/client-logger" "6.4.17" - "@storybook/core-events" "6.4.17" + core-js "^3.8.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + +"@storybook/client-api@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.4.18.tgz#61c7c90f3f099e4d3bcc36576d2adbe2e5ef6eee" + integrity sha512-ua2Q692Fz2b3q5M/Qzjixg2LArwrcHGBmht06bNw/jrRfyFeTUHOhh5BT7LxSEetUgHATH/Y1GW40xza9rXFNw== + dependencies: + "@storybook/addons" "6.4.18" + "@storybook/channel-postmessage" "6.4.18" + "@storybook/channels" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/core-events" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/store" "6.4.17" + "@storybook/store" "6.4.18" "@types/qs" "^6.9.5" "@types/webpack-env" "^1.16.0" core-js "^3.8.2" @@ -1617,14 +1617,6 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/client-logger@6.4.14": - version "6.4.14" - resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.4.14.tgz#a7aed982407e4146548f9ac4b3af5eba24cd045e" - integrity sha512-4VmFWZxhpeiG5fDhfqAyQbCfXZSBKS4fNKf35ABWiHStZRDndxml8K5WFtmOmMvVzjrGQx8HesenYMawK6xo/Q== - dependencies: - core-js "^3.8.2" - global "^4.4.0" - "@storybook/client-logger@6.4.17": version "6.4.17" resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.4.17.tgz#50652859592c489b671f010455b8ce85d21a1b3d" @@ -1633,35 +1625,13 @@ core-js "^3.8.2" global "^4.4.0" -"@storybook/components@6.4.14": - version "6.4.14" - resolved "https://registry.npmjs.org/@storybook/components/-/components-6.4.14.tgz#546b34fe3feb09e670b76ff71d889bf5f566f1e4" - integrity sha512-M7unerbOnvg+UN7qPxBCBWzK/boVdSSQxRiPAr1OL3M4OyEU8+TNPdQeAG0aF4zqtU0BrsDf4E85EznoMXUiFQ== +"@storybook/client-logger@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.4.18.tgz#4ad8ea7d67b17e5db8f15cffcc2f984df3479462" + integrity sha512-ciBaASMaB2ZPksbuyDbp3++5SZxbhcihEpl+RQcAVV8g+TUyBZKIcHt8HNHicTczz5my1EydZovMh1IkSBMICA== dependencies: - "@popperjs/core" "^2.6.0" - "@storybook/client-logger" "6.4.14" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/theming" "6.4.14" - "@types/color-convert" "^2.0.0" - "@types/overlayscrollbars" "^1.12.0" - "@types/react-syntax-highlighter" "11.0.5" - color-convert "^2.0.1" core-js "^3.8.2" - fast-deep-equal "^3.1.3" global "^4.4.0" - lodash "^4.17.21" - markdown-to-jsx "^7.1.3" - memoizerific "^1.11.3" - overlayscrollbars "^1.13.1" - polished "^4.0.5" - prop-types "^15.7.2" - react-colorful "^5.1.2" - react-popper-tooltip "^3.1.1" - react-syntax-highlighter "^13.5.3" - react-textarea-autosize "^8.3.0" - regenerator-runtime "^0.13.7" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" "@storybook/components@6.4.17": version "6.4.17" @@ -1693,21 +1663,51 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/core-client@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/core-client/-/core-client-6.4.17.tgz#baff9629a0723f9485d608c00357d45921a78a0d" - integrity sha512-uXO+DW5XI6fWLtQIBIBlBFeYGsy2qZEe3lxxXwBHwIjsDq53/1CmhEPuzC3jAsy5ddeKC2yEEHUdy3d3wkusIQ== +"@storybook/components@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/components/-/components-6.4.18.tgz#1f3eba9ab69a09b9468af0126d6e7ab040655ca4" + integrity sha512-LAPKYWgB6S10Vzt0IWa1Ihf9EAuQOGxlqehTuxYLOwMOKbto8iEbGRse/XaQfxdZf/RbmOL4u+7nVRROWgOEjg== dependencies: - "@storybook/addons" "6.4.17" - "@storybook/channel-postmessage" "6.4.17" - "@storybook/channel-websocket" "6.4.17" - "@storybook/client-api" "6.4.17" - "@storybook/client-logger" "6.4.17" - "@storybook/core-events" "6.4.17" + "@popperjs/core" "^2.6.0" + "@storybook/client-logger" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/preview-web" "6.4.17" - "@storybook/store" "6.4.17" - "@storybook/ui" "6.4.17" + "@storybook/theming" "6.4.18" + "@types/color-convert" "^2.0.0" + "@types/overlayscrollbars" "^1.12.0" + "@types/react-syntax-highlighter" "11.0.5" + color-convert "^2.0.1" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + markdown-to-jsx "^7.1.3" + memoizerific "^1.11.3" + overlayscrollbars "^1.13.1" + polished "^4.0.5" + prop-types "^15.7.2" + react-colorful "^5.1.2" + react-popper-tooltip "^3.1.1" + react-syntax-highlighter "^13.5.3" + react-textarea-autosize "^8.3.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + +"@storybook/core-client@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/core-client/-/core-client-6.4.18.tgz#7f2feb961864dcf6de501a94a41900fd36b43657" + integrity sha512-F9CqW31Mr9Qde90uqPorpkiS+P7UteKYmdHlV0o0czeWaL+MEhpY+3pRJuRIIjX5C7Vc89TvljMqs37Khakmdg== + dependencies: + "@storybook/addons" "6.4.18" + "@storybook/channel-postmessage" "6.4.18" + "@storybook/channel-websocket" "6.4.18" + "@storybook/client-api" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/core-events" "6.4.18" + "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/preview-web" "6.4.18" + "@storybook/store" "6.4.18" + "@storybook/ui" "6.4.18" airbnb-js-shims "^2.2.1" ansi-to-html "^0.6.11" core-js "^3.8.2" @@ -1719,10 +1719,10 @@ unfetch "^4.2.0" util-deprecate "^1.0.2" -"@storybook/core-common@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/core-common/-/core-common-6.4.17.tgz#bd2b14cfd1473f5f31f40c747afb62ea4a4ada6e" - integrity sha512-aOSG5Yvd8eoZsjvVlk7sS8iRXWT/dleHoHPXtKmHJnGcIZ1dcgr4wZqoOvL8dGhNNoU4Wx9dkJepqHD0+E/UgA== +"@storybook/core-common@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/core-common/-/core-common-6.4.18.tgz#0688a0a4a80cdbc161966c5a7ff49e755d64bbab" + integrity sha512-y4e43trNyQ3/v0Wpi240on7yVooUQvJBhJxOGEfcxAMRtcDa0ZCxHO4vAFX3k3voQOSmiXItXfJ1eGo/K+u0Fw== dependencies: "@babel/core" "^7.12.10" "@babel/plugin-proposal-class-properties" "^7.12.1" @@ -1745,7 +1745,7 @@ "@babel/preset-react" "^7.12.10" "@babel/preset-typescript" "^7.12.7" "@babel/register" "^7.12.1" - "@storybook/node-logger" "6.4.17" + "@storybook/node-logger" "6.4.18" "@storybook/semver" "^7.3.2" "@types/node" "^14.0.10" "@types/pretty-hrtime" "^1.0.0" @@ -1774,13 +1774,6 @@ util-deprecate "^1.0.2" webpack "4" -"@storybook/core-events@6.4.14": - version "6.4.14" - resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.4.14.tgz#37293c0fce703f2643cec6f24fc6ef7c40e30ded" - integrity sha512-9QFltg2mxTDjMBfmVtFHtrAEPY/i0oVp2kVdTWo6g05cPffYKAjNUnUVjUl7yiqcQmdEcdqUUQ0ut3xgmcYi/A== - dependencies: - core-js "^3.8.2" - "@storybook/core-events@6.4.17": version "6.4.17" resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.4.17.tgz#ad70c883673a2060f1c7c4aa8f5926fc14119f4a" @@ -1788,22 +1781,29 @@ dependencies: core-js "^3.8.2" -"@storybook/core-server@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/core-server/-/core-server-6.4.17.tgz#16f31635565c19248a45a793a7c2150597f3a4cc" - integrity sha512-wXYF4VD2EJ/6uFK+wAo/TgUyfD/lfMzzbAw2gBZAjYp7y7Zwj3svGqUfkFuPQG0/E9gmQfEmlyhTPPZImBFeBg== +"@storybook/core-events@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.4.18.tgz#630a19425eb387c6134f29b967c30458c65f7ea8" + integrity sha512-lCT3l0rFs6CuVpD8+mwmj1lUTomGErySTxi0KmVd2AWQj8kJL90EfS0jHSU5JIXScDvuwXDXLLmvMfqNU+zHdg== + dependencies: + core-js "^3.8.2" + +"@storybook/core-server@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/core-server/-/core-server-6.4.18.tgz#520935f7f330a734488e733ad4cf15a9556679b5" + integrity sha512-7e2QUtD8/TE14P9X/xsBDMBbNVi/etTtMKKhsG2TG25daRz+6qadbM9tNP0YwvIDk452cNYJkjflV48mf5+ZEA== dependencies: "@discoveryjs/json-ext" "^0.5.3" - "@storybook/builder-webpack4" "6.4.17" - "@storybook/core-client" "6.4.17" - "@storybook/core-common" "6.4.17" - "@storybook/core-events" "6.4.17" + "@storybook/builder-webpack4" "6.4.18" + "@storybook/core-client" "6.4.18" + "@storybook/core-common" "6.4.18" + "@storybook/core-events" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/csf-tools" "6.4.17" - "@storybook/manager-webpack4" "6.4.17" - "@storybook/node-logger" "6.4.17" + "@storybook/csf-tools" "6.4.18" + "@storybook/manager-webpack4" "6.4.18" + "@storybook/node-logger" "6.4.18" "@storybook/semver" "^7.3.2" - "@storybook/store" "6.4.17" + "@storybook/store" "6.4.18" "@types/node" "^14.0.10" "@types/node-fetch" "^2.5.7" "@types/pretty-hrtime" "^1.0.0" @@ -1836,18 +1836,18 @@ webpack "4" ws "^8.2.3" -"@storybook/core@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/core/-/core-6.4.17.tgz#61892e2eb484a44fc9d69515c3068b1f64173327" - integrity sha512-wquJcEebw9kXJ7pThcmEsDNK0ykd3ir0uL5tkBzPGNIj7dozpzy24Fo9JSr0rNWHNtE7JczdIAQTcumowLTDig== +"@storybook/core@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/core/-/core-6.4.18.tgz#56f7bb0f20dbcfa205d860022b7bf30bf42fd472" + integrity sha512-7DTMAEXiBIwd1jgalbsZiXCiS2Be9MKKsr6GQdf3WaBm0WYV067oN9jcUY5IgNtJX06arT4Ykp+CGG/TR+sLlw== dependencies: - "@storybook/core-client" "6.4.17" - "@storybook/core-server" "6.4.17" + "@storybook/core-client" "6.4.18" + "@storybook/core-server" "6.4.18" -"@storybook/csf-tools@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-6.4.17.tgz#e7d2b9de95317d706657c294d7beee1d7b307ec4" - integrity sha512-GnaO1dX4wTvMKBthbbHLHcrDTXwZ7PooZmT1fTCeokzaobZzyv1cUtF1hlPQa3zA75kRE5AznJ0jmBVhHe0/9Q== +"@storybook/csf-tools@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-6.4.18.tgz#cdd40b543f9bea79c1481c236868b8ea04af6bd7" + integrity sha512-KtwxKrkndEyvyAiBliI6m4yMFMvnyI4fOjU8t1qCit/0gjutOF5JxmmZ+H8FSI5dIyibEOzQmzHe0MyStAjCEQ== dependencies: "@babel/core" "^7.12.10" "@babel/generator" "^7.12.11" @@ -1874,20 +1874,20 @@ dependencies: lodash "^4.17.15" -"@storybook/manager-webpack4@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/manager-webpack4/-/manager-webpack4-6.4.17.tgz#8fc6d5dba0587446defe78ced67b7033886c4c47" - integrity sha512-ekHudBR8FVSE475YQZZs9sqwou7YqFv03hNVOcvIJ36cZBgMbSkG8q50cK4uru2xCOedTK15SKIoFZQQ77cmQQ== +"@storybook/manager-webpack4@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/manager-webpack4/-/manager-webpack4-6.4.18.tgz#5317c917dbdaf4cf8721647551a785eb13c04146" + integrity sha512-6oX1KrIJBoY4vdZiMftJNusv+Bm8pegVjdJ+aZcbr/41x7ufP3tu5UKebEXDH0UURXtLw0ffl+OgojewGdpC1Q== dependencies: "@babel/core" "^7.12.10" "@babel/plugin-transform-template-literals" "^7.12.1" "@babel/preset-react" "^7.12.10" - "@storybook/addons" "6.4.17" - "@storybook/core-client" "6.4.17" - "@storybook/core-common" "6.4.17" - "@storybook/node-logger" "6.4.17" - "@storybook/theming" "6.4.17" - "@storybook/ui" "6.4.17" + "@storybook/addons" "6.4.18" + "@storybook/core-client" "6.4.18" + "@storybook/core-common" "6.4.18" + "@storybook/node-logger" "6.4.18" + "@storybook/theming" "6.4.18" + "@storybook/ui" "6.4.18" "@types/node" "^14.0.10" "@types/webpack" "^4.41.26" babel-loader "^8.0.0" @@ -1916,10 +1916,10 @@ webpack-dev-middleware "^3.7.3" webpack-virtual-modules "^0.2.2" -"@storybook/node-logger@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.4.17.tgz#14fe3091b2030413c2f43f0de3e9408b27591d9c" - integrity sha512-gymFKjmOdi9fAJCaM4C8I/5Go4hPsOAcVNixpjAQYsvNQQZ1Yjm2zcSdD+QOuLJ36NTxgOFxT4ESbC2AfSjyqA== +"@storybook/node-logger@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.4.18.tgz#8759761ba7526b2fa03a1a08fe82d6d892d7a072" + integrity sha512-wY1qt4XOXtJJdQ+DrO3RijtiwVFqWuWetvCY4RV4lge5yk0FP5Q+MTpmjazYodAvGPUIP0LK9bvEDLwXa0JUfw== dependencies: "@types/npmlog" "^4.1.2" chalk "^4.1.0" @@ -1927,17 +1927,17 @@ npmlog "^5.0.1" pretty-hrtime "^1.0.3" -"@storybook/preview-web@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/preview-web/-/preview-web-6.4.17.tgz#d29bbfa8f66428ef5e2202c4c364c1892d8cbf7b" - integrity sha512-fJIE/LO7I09w334AH71ojRpIiHLQrBUidkZlIQbjEmHn/GZBTePlf3CevrERA12FbCLoUbeS5nadk2dEg6YnUw== +"@storybook/preview-web@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/preview-web/-/preview-web-6.4.18.tgz#47c908bf27d2089ccf3296c376a6f5b1e8674b5a" + integrity sha512-0x64uLdGhIOk9hIuRKTHFdP7+iEHyjAOi5U4jtwqFfDtG4n4zxEGSsUWij7pTR2rAYf7g2NWIbAM7qb1AqqcLQ== dependencies: - "@storybook/addons" "6.4.17" - "@storybook/channel-postmessage" "6.4.17" - "@storybook/client-logger" "6.4.17" - "@storybook/core-events" "6.4.17" + "@storybook/addons" "6.4.18" + "@storybook/channel-postmessage" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/core-events" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/store" "6.4.17" + "@storybook/store" "6.4.18" ansi-to-html "^0.6.11" core-js "^3.8.2" global "^4.4.0" @@ -1962,22 +1962,22 @@ react-docgen-typescript "^2.0.0" tslib "^2.0.0" -"@storybook/react@^6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/react/-/react-6.4.17.tgz#c12f22c3365d4213c662bf1e26520e72c68b0c33" - integrity sha512-hAMEyMcWC5fEdzXOYr0S9/QHclXbbJpl7Vl9dd56wxbHx4FFwcJ7R5hroLntPsHXU+rGTF9/EqehmEa/Jd0l4w== +"@storybook/react@^6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/react/-/react-6.4.18.tgz#22624af56a9873c6616b5dc6a1e30c968bac95d2" + integrity sha512-dKxwsvJEGTm/aNIJSJMI4MImsI4EhmBa42FtwVvtFkrokuMf2CsmTJsaaAh+1or9SKGTiFuGsYDGhX5joE3XUQ== dependencies: "@babel/preset-flow" "^7.12.1" "@babel/preset-react" "^7.12.10" "@pmmmwh/react-refresh-webpack-plugin" "^0.5.1" - "@storybook/addons" "6.4.17" - "@storybook/core" "6.4.17" - "@storybook/core-common" "6.4.17" + "@storybook/addons" "6.4.18" + "@storybook/core" "6.4.18" + "@storybook/core-common" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/node-logger" "6.4.17" + "@storybook/node-logger" "6.4.18" "@storybook/react-docgen-typescript-plugin" "1.0.2-canary.253f8c1.0" "@storybook/semver" "^7.3.2" - "@storybook/store" "6.4.17" + "@storybook/store" "6.4.18" "@types/webpack-env" "^1.16.0" babel-plugin-add-react-displayname "^0.0.5" babel-plugin-named-asset-import "^0.3.1" @@ -1992,12 +1992,12 @@ ts-dedent "^2.0.0" webpack "4" -"@storybook/router@6.4.14": - version "6.4.14" - resolved "https://registry.npmjs.org/@storybook/router/-/router-6.4.14.tgz#46fd46eadafc0d6b647be13702704c5fcf8f11e3" - integrity sha512-5+tePyINtwPYm4izgOBZ2sX2ViWtfmmO2vwOAPlWWEGzsRosVQsGMdZv1R8rk4Jl/TotMjlTmd8I1/BufEeIeQ== +"@storybook/router@6.4.17": + version "6.4.17" + resolved "https://registry.npmjs.org/@storybook/router/-/router-6.4.17.tgz#d53c4f9a4ccaa21a7bbe8d875a1a81c9dba2a6f2" + integrity sha512-GLhzth83BB2BbUkM/+ld2JITIbDQtzFLs/CnZZQKq6aR93Kou6VK2epHnIwrPyWbP6rsGavR/8L/UWeBdwwTrQ== dependencies: - "@storybook/client-logger" "6.4.14" + "@storybook/client-logger" "6.4.17" core-js "^3.8.2" fast-deep-equal "^3.1.3" global "^4.4.0" @@ -2009,12 +2009,12 @@ react-router-dom "^6.0.0" ts-dedent "^2.0.0" -"@storybook/router@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/router/-/router-6.4.17.tgz#d53c4f9a4ccaa21a7bbe8d875a1a81c9dba2a6f2" - integrity sha512-GLhzth83BB2BbUkM/+ld2JITIbDQtzFLs/CnZZQKq6aR93Kou6VK2epHnIwrPyWbP6rsGavR/8L/UWeBdwwTrQ== +"@storybook/router@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/router/-/router-6.4.18.tgz#8803dd78277f8602d6c11dae56f6229474dfa54c" + integrity sha512-itvSWHhG1X/NV1sMlwP1qKtF0HfiIaAHImr0LwQ2K2F6/CI11W68dJAs4WBUdwzA0+H0Joyu/2a/6mCQHcee1A== dependencies: - "@storybook/client-logger" "6.4.17" + "@storybook/client-logger" "6.4.18" core-js "^3.8.2" fast-deep-equal "^3.1.3" global "^4.4.0" @@ -2050,14 +2050,14 @@ prettier ">=2.2.1 <=2.3.0" regenerator-runtime "^0.13.7" -"@storybook/store@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/store/-/store-6.4.17.tgz#a68b687628b82f8ca0ef2c74f5d65f5a30e94f86" - integrity sha512-0rWk8u7gtzBOp5NvuIrL6abBHaDxax7e+yBPvU9tR0GZ7X0ALhOhJFRIo+lW9sZTUrcuSinOJ8Acyb0ZvnYCkg== +"@storybook/store@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/store/-/store-6.4.18.tgz#3b693c9d5555d5cfc04e2318e104746d9d55ad66" + integrity sha512-Vl5oCs/9fP1gUgfgMHTBsnYbwAAoaR93/bzDBeOHI3eo5x9uzzJtA4zcRmEiKahR/wgwGacpWy90JrIX469PDQ== dependencies: - "@storybook/addons" "6.4.17" - "@storybook/client-logger" "6.4.17" - "@storybook/core-events" "6.4.17" + "@storybook/addons" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/core-events" "6.4.18" "@storybook/csf" "0.0.2--canary.87bc651.0" core-js "^3.8.2" fast-deep-equal "^3.1.3" @@ -2071,24 +2071,6 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/theming@6.4.14": - version "6.4.14" - resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.4.14.tgz#f034914eb1853a80f588c7c141d47af0595f6d1f" - integrity sha512-kqmXNnIoOSAS4cgr9PitMgVrOps725O99eTsJNxB6J1Ide0CsA5v2tV6AmQn/scnpCQNr8uSjZerNlEcl/ensg== - dependencies: - "@emotion/core" "^10.1.1" - "@emotion/is-prop-valid" "^0.8.6" - "@emotion/styled" "^10.0.27" - "@storybook/client-logger" "6.4.14" - core-js "^3.8.2" - deep-object-diff "^1.1.0" - emotion-theming "^10.0.27" - global "^4.4.0" - memoizerific "^1.11.3" - polished "^4.0.5" - resolve-from "^5.0.0" - ts-dedent "^2.0.0" - "@storybook/theming@6.4.17": version "6.4.17" resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.4.17.tgz#f0a03d2d3239638ac171e97a8f089ee2656a8287" @@ -2107,21 +2089,39 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" -"@storybook/ui@6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@storybook/ui/-/ui-6.4.17.tgz#926aa1dbb4d3f9a28afe6edd6a0e5cb667be8e19" - integrity sha512-vBYV8PmvhYgMjjTRLtOHIisGqr1nfajAgOC+wfYvGLbF0npVEt5PfDieG1LTRc1OaItWLpKKJcByqSfL/y9Qow== +"@storybook/theming@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.4.18.tgz#05365cc1d3dab5d71b80a82928fc5188106a0ed6" + integrity sha512-1o0w2eP+8sXUesdtXpZR4Yvayp1h3xvK7l9+wuHh+1uCy+EvD5UI9d1HvU5kt5fw7XAJJcInaVAmyAbpwct0TQ== dependencies: "@emotion/core" "^10.1.1" - "@storybook/addons" "6.4.17" - "@storybook/api" "6.4.17" - "@storybook/channels" "6.4.17" - "@storybook/client-logger" "6.4.17" - "@storybook/components" "6.4.17" - "@storybook/core-events" "6.4.17" - "@storybook/router" "6.4.17" + "@emotion/is-prop-valid" "^0.8.6" + "@emotion/styled" "^10.0.27" + "@storybook/client-logger" "6.4.18" + core-js "^3.8.2" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.27" + global "^4.4.0" + memoizerific "^1.11.3" + polished "^4.0.5" + resolve-from "^5.0.0" + ts-dedent "^2.0.0" + +"@storybook/ui@6.4.18": + version "6.4.18" + resolved "https://registry.npmjs.org/@storybook/ui/-/ui-6.4.18.tgz#3ceaf6b317f8f2c1d7d1cdc49daaac7eaf10af6b" + integrity sha512-f2ckcLvEyA9CRcu6W2I2CyEbUnU4j3h5Nz0N40YZ2uRMVNQY2xPywAFZVySZIJAaum/5phDfnOD0Feap/Q6zVQ== + dependencies: + "@emotion/core" "^10.1.1" + "@storybook/addons" "6.4.18" + "@storybook/api" "6.4.18" + "@storybook/channels" "6.4.18" + "@storybook/client-logger" "6.4.18" + "@storybook/components" "6.4.18" + "@storybook/core-events" "6.4.18" + "@storybook/router" "6.4.18" "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.4.17" + "@storybook/theming" "6.4.18" copy-to-clipboard "^3.3.1" core-js "^3.8.2" core-js-pure "^3.8.2" diff --git a/yarn.lock b/yarn.lock index d13edb974c..9f41538dd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1392,17 +1392,17 @@ resolved "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-5.0.2.tgz#b23080fa35520e993a8a37a0f5bca26aa4650a48" integrity sha512-NBEJlHWrhQucLhZGHtSxM2loSaNUMajC7KOYJLyfcdW/6goVoff2HoYI3bz8YCDN0wKGbxtUL0gx2dvHpvnWlw== -"@changesets/apply-release-plan@^5.0.1": - version "5.0.1" - resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-5.0.1.tgz#ed3e30550f787ef1b72f0a51e29a54d244123109" - integrity sha512-ltYLM/PPoL1Un9hnNCbUac25FWonJvIZ/9C3O4UyZ/k4rir9FGvH6KLtMOiPEAJWnXmaHeRDr06MzohuXOnmvw== +"@changesets/apply-release-plan@^5.0.4": + version "5.0.4" + resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-5.0.4.tgz#fefffecf73b59dbee7ae905b3c6a2e64c489f0cb" + integrity sha512-czayDIrgC8qBnqwClvh9nxjCMem+XZG7xtfdYwq3dxpzA30qGppcI0i088VYug5RCFR+l1N+HUvkujSZuBK65w== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/config" "^1.6.1" + "@changesets/config" "^1.6.4" "@changesets/get-version-range-type" "^0.3.2" - "@changesets/git" "^1.1.2" - "@changesets/types" "^4.0.1" - "@manypkg/get-packages" "^1.0.1" + "@changesets/git" "^1.3.0" + "@changesets/types" "^4.0.2" + "@manypkg/get-packages" "^1.1.3" detect-indent "^6.0.0" fs-extra "^7.0.1" lodash.startcase "^4.4.0" @@ -1411,45 +1411,45 @@ resolve-from "^5.0.0" semver "^5.4.1" -"@changesets/assemble-release-plan@^5.0.1": - version "5.0.1" - resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.0.1.tgz#80e9b750705677eb2d6356c581ed9c2e97fd68e7" - integrity sha512-KQqafvScTFQ/4Q2LpLmDYhU47LWvIGcgVS8tzKU8fBvRdKuLGQXe42VYbwVM0cHIkFd/b6YFn+H2QMdKC2MjIQ== +"@changesets/assemble-release-plan@^5.0.5": + version "5.0.5" + resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.0.5.tgz#60e4adc9bf458dd6ec38dee78d8f1bbeddfe3936" + integrity sha512-ejCVSM4I1jgaNi30we3/qltj2NQtS68w7C3H8Gvb6ZOvbIpAW/Tr0uMmPgRj4Vzkez5+fx0If02AvOdssz1btA== dependencies: "@babel/runtime" "^7.10.4" "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.2.2" - "@changesets/types" "^4.0.1" - "@manypkg/get-packages" "^1.0.1" + "@changesets/get-dependents-graph" "^1.3.0" + "@changesets/types" "^4.0.2" + "@manypkg/get-packages" "^1.1.3" semver "^5.4.1" "@changesets/cli@^2.14.0": - version "2.17.0" - resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.17.0.tgz#cc7ff4f64d029ddd6d87020a012c8cf8c7adde58" - integrity sha512-UyraYwYst1lTjef+8i80XQ6SqsLaGwi4Sgn9YuDf2xdHA9m+5qQXshHvHVjaTdPTA09rqMBk9yeO7vmAqF4+vQ== + version "2.20.0" + resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.20.0.tgz#f24dbd38bd4bc47bf083e59b6bb9cbf531465808" + integrity sha512-IUYSgZKtS+wXPD5hxfnCfZ1JWCbBI0CRrhxpkgVKcXDwpxiRU8stCwuSuVj14kiYlThuH2zL0/ZuGvhF4r28Gg== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/apply-release-plan" "^5.0.1" - "@changesets/assemble-release-plan" "^5.0.1" - "@changesets/config" "^1.6.1" + "@changesets/apply-release-plan" "^5.0.4" + "@changesets/assemble-release-plan" "^5.0.5" + "@changesets/config" "^1.6.4" "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.2.2" - "@changesets/get-release-plan" "^3.0.1" - "@changesets/git" "^1.1.2" + "@changesets/get-dependents-graph" "^1.3.0" + "@changesets/get-release-plan" "^3.0.5" + "@changesets/git" "^1.3.0" "@changesets/logger" "^0.0.5" - "@changesets/pre" "^1.0.7" - "@changesets/read" "^0.5.0" - "@changesets/types" "^4.0.1" - "@changesets/write" "^0.1.5" - "@manypkg/get-packages" "^1.0.1" + "@changesets/pre" "^1.0.9" + "@changesets/read" "^0.5.3" + "@changesets/types" "^4.0.2" + "@changesets/write" "^0.1.6" + "@manypkg/get-packages" "^1.1.3" + "@types/is-ci" "^3.0.0" "@types/semver" "^6.0.0" - boxen "^1.3.0" chalk "^2.1.0" enquirer "^2.3.0" external-editor "^3.1.0" fs-extra "^7.0.1" human-id "^1.0.2" - is-ci "^2.0.0" + is-ci "^3.0.1" meow "^6.0.0" outdent "^0.5.0" p-limit "^2.2.0" @@ -1459,16 +1459,16 @@ term-size "^2.1.0" tty-table "^2.8.10" -"@changesets/config@^1.6.1": - version "1.6.1" - resolved "https://registry.npmjs.org/@changesets/config/-/config-1.6.1.tgz#e9b1636fd56a74411c493c924e6ffa07d7d26091" - integrity sha512-aQTo6ODvhsvnSFszMP1YbJyAi1DtE1Pes9rL+G+KYJiAOA6k5RzbiKOarjo+ZkKXpX0G3CBAbOO8jXOX4xG7cQ== +"@changesets/config@^1.6.4": + version "1.6.4" + resolved "https://registry.npmjs.org/@changesets/config/-/config-1.6.4.tgz#c904c84f067b49c09b378618b751089dd29ed0b7" + integrity sha512-WWa8eR8GzS/p2atLc/+5UEDn7fsRCZ+/sShLkB/3efVbTkSTB1PwoKwQRXLYXM1DY289T7UnJT4HLZA3Gcreww== dependencies: "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.2.2" + "@changesets/get-dependents-graph" "^1.3.0" "@changesets/logger" "^0.0.5" - "@changesets/types" "^4.0.1" - "@manypkg/get-packages" "^1.0.1" + "@changesets/types" "^4.0.2" + "@manypkg/get-packages" "^1.1.3" fs-extra "^7.0.1" micromatch "^4.0.2" @@ -1479,44 +1479,44 @@ dependencies: extendable-error "^0.1.5" -"@changesets/get-dependents-graph@^1.2.2": - version "1.2.2" - resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.2.2.tgz#7a2238f3d1a023de83d37b727a0da15826e88d73" - integrity sha512-3zJRw6TcexmOrmIZNOXpIRsZtqtrdmlzbqp4+V0VgnBvTxz16rqCS9VBsBqFYeJDWFj3soOlHUMeTwLghr18DA== +"@changesets/get-dependents-graph@^1.3.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.3.0.tgz#892bbb63406911baac1f77a31303620492d466ec" + integrity sha512-4VHQWEluWySPgDdkL94YNxrEjDb9nwNFw515sWDmVrlfpQN5qaP1hdaotrp4mJm4ky85t4cTlrWSP+CTY7IDbw== dependencies: - "@changesets/types" "^4.0.1" - "@manypkg/get-packages" "^1.0.1" + "@changesets/types" "^4.0.2" + "@manypkg/get-packages" "^1.1.3" chalk "^2.1.0" fs-extra "^7.0.1" semver "^5.4.1" -"@changesets/get-release-plan@^3.0.1": - version "3.0.1" - resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.1.tgz#c98a34321eac9e4187098893ff8dadb6f90ad89c" - integrity sha512-HTZeEPvLlcWMWKxLrzQNLQWKDDN1lUKvaOV+hl/yBhgtyJECljJJzd3IRaKqCSWMrYKNaaEcmunTtZ4oaeoK9w== +"@changesets/get-release-plan@^3.0.5": + version "3.0.5" + resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.5.tgz#c05cff031bba737c8dd236afe97e77b007ee44a3" + integrity sha512-67td3LA1RTJpY5Q+wJaTTRtAjZ2suAhDfj3VRjFv0gCgUPXs8rNx17n9UPbegPTQjeTS1r7hVRVifycmT0fQtA== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/assemble-release-plan" "^5.0.1" - "@changesets/config" "^1.6.1" - "@changesets/pre" "^1.0.7" - "@changesets/read" "^0.5.0" - "@changesets/types" "^4.0.1" - "@manypkg/get-packages" "^1.0.1" + "@changesets/assemble-release-plan" "^5.0.5" + "@changesets/config" "^1.6.4" + "@changesets/pre" "^1.0.9" + "@changesets/read" "^0.5.3" + "@changesets/types" "^4.0.2" + "@manypkg/get-packages" "^1.1.3" "@changesets/get-version-range-type@^0.3.2": version "0.3.2" resolved "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz#8131a99035edd11aa7a44c341cbb05e668618c67" integrity sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg== -"@changesets/git@^1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@changesets/git/-/git-1.1.2.tgz#248d4418bcb2d4f198852409cfcbd06a1fcb0424" - integrity sha512-dfza8elsIwcYVa4fFzLaPs4+AkoCFiW3sfzkkC7WR+rG9j+zZh7CelzVpnoiAbEI2QOzeCbZKMoLSvBPgHhB1g== +"@changesets/git@^1.3.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@changesets/git/-/git-1.3.0.tgz#7f62e1983552efecb738054db5178eef77f41241" + integrity sha512-Ydj4lWX33d2PCDaTXOMSbyTjgk1go1V6EyXjKTmOV7nB/qvgKdDZLSt+AexKWKp3Ac2FTrtVnl9G5gMNVYNmuQ== dependencies: "@babel/runtime" "^7.10.4" "@changesets/errors" "^0.1.4" - "@changesets/types" "^4.0.1" - "@manypkg/get-packages" "^1.0.1" + "@changesets/types" "^4.0.2" + "@manypkg/get-packages" "^1.1.3" is-subdir "^1.1.1" spawndamnit "^2.0.0" @@ -1527,51 +1527,51 @@ dependencies: chalk "^2.1.0" -"@changesets/parse@^0.3.9": - version "0.3.9" - resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.9.tgz#c518792b05f15ab418d58dc1cea81601556e845e" - integrity sha512-XoTEkMpvRRVxSlhvOaK4YSFM+RZhYFTksxRh7ieNkb6pMxkpq8MOYSi/07BuqkODn4dJEMOoSy3RzL99P6FyqA== +"@changesets/parse@^0.3.11": + version "0.3.11" + resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.11.tgz#b53485f36152e6e9a097b56fc46cafb6705ce2b5" + integrity sha512-w5/X8KijcCrvv5lHimXIBR9o35c78niiBoesBjBUlWeifwPz0DHc/lzVYJKRkA5w0BGqft6T/9hKI68GaYj5wA== dependencies: - "@changesets/types" "^4.0.1" + "@changesets/types" "^4.0.2" js-yaml "^3.13.1" -"@changesets/pre@^1.0.7": - version "1.0.7" - resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.7.tgz#caf6430c90b8ac6d58c9cd90a19558ab06b19b88" - integrity sha512-oUU6EL4z0AIyCv/EscQFxxJsQfc9/AcSpqAGbdZrLXwshUWTXsJHMWlE3/+iSIyQ+I+/xtxbBxnqDUpUU3TOOg== +"@changesets/pre@^1.0.9": + version "1.0.9" + resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.9.tgz#f1a0efea42733c25ef4a782377b2ac61023bd1b7" + integrity sha512-F3+qMun89KlynecBD15fEpwGT/KxbYb3WGeut6w1xhZb0u7V/jdcPy9b+kJ2xmBqFZLn1WteWIP96IjxS57H7A== dependencies: "@babel/runtime" "^7.10.4" "@changesets/errors" "^0.1.4" - "@changesets/types" "^4.0.1" - "@manypkg/get-packages" "^1.0.1" + "@changesets/types" "^4.0.2" + "@manypkg/get-packages" "^1.1.3" fs-extra "^7.0.1" -"@changesets/read@^0.5.0": - version "0.5.0" - resolved "https://registry.npmjs.org/@changesets/read/-/read-0.5.0.tgz#52f7a10f6baebf97172e62035ee8345652c5a1c0" - integrity sha512-A2OJ+vgfvbUaLx2yKyHH+tapa+DUd2NtpFpVuxjUqv0zirjqju20z1bziqaqpIQSf/rXPuoc09vp5w4VakraHg== +"@changesets/read@^0.5.3": + version "0.5.3" + resolved "https://registry.npmjs.org/@changesets/read/-/read-0.5.3.tgz#5afb14ee0e806751c255e94fe8742843a9b12487" + integrity sha512-zoj5NjNR4AhiGXz6aHTxsBLojChHgDOSbz6VfAVxMKX7tF7UhyNYptG2VEbSjxeamNKABx6k1pkM2IyVVlOcbQ== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/git" "^1.1.2" + "@changesets/git" "^1.3.0" "@changesets/logger" "^0.0.5" - "@changesets/parse" "^0.3.9" - "@changesets/types" "^4.0.1" + "@changesets/parse" "^0.3.11" + "@changesets/types" "^4.0.2" chalk "^2.1.0" fs-extra "^7.0.1" p-filter "^2.1.0" -"@changesets/types@^4.0.1": - version "4.0.1" - resolved "https://registry.npmjs.org/@changesets/types/-/types-4.0.1.tgz#85cf3cc32baff0691112d9d15fc21fbe022c9f0a" - integrity sha512-zVfv752D8K2tjyFmxU/vnntQ+dPu+9NupOSguA/2Zuym4tVxRh0ylArgKZ1bOAi2eXfGlZMxJU/kj7uCSI15RQ== +"@changesets/types@^4.0.1", "@changesets/types@^4.0.2": + version "4.0.2" + resolved "https://registry.npmjs.org/@changesets/types/-/types-4.0.2.tgz#d20e1e45bdc96a97cc509c655e708b53a9292465" + integrity sha512-OeDaB7D+WVy/ErymPzFm58IeGvz4DOl+oedyZETfnkfMezF/Uhrm1Ub6MHrO5LcAaQTW+ptDmr0fmaVyoTxgHw== -"@changesets/write@^0.1.5": - version "0.1.5" - resolved "https://registry.npmjs.org/@changesets/write/-/write-0.1.5.tgz#97574d95c8e48c3bbb1173802672f9a64d1b7fef" - integrity sha512-AYVSCH7on/Cyzo/8lVfqlsXmyKl3JhbNu9yHApdLPhHAzv5wqoHiZlMDkmd+AA67SRqzK2lDs4BcIojK+uWeIA== +"@changesets/write@^0.1.6": + version "0.1.6" + resolved "https://registry.npmjs.org/@changesets/write/-/write-0.1.6.tgz#798d882bada93f391902a854a17965f3b80a5201" + integrity sha512-JWE2gJs9eHhorxqembkf43fllKlCz+sp1TJKSheaWfhWILMHPdfa/xQG4+sMZkISo1qZ+IlJyiBLha6iGGjXyA== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/types" "^4.0.1" + "@changesets/types" "^4.0.2" fs-extra "^7.0.1" human-id "^1.0.2" prettier "^1.19.1" @@ -3477,17 +3477,6 @@ find-up "^4.1.0" fs-extra "^8.1.0" -"@manypkg/get-packages@^1.0.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.1.tgz#7c7e72d0061ab2e61d2ce4da58ce91290a60ac8d" - integrity sha512-J6VClfQSVgR6958eIDTGjfdCrELy1eT+SHeoSMomnvRQVktZMnEA5edIr5ovRFNw5y+Bk/jyoevPzGYod96mhw== - dependencies: - "@babel/runtime" "^7.5.5" - "@manypkg/find-root" "^1.1.0" - fs-extra "^8.1.0" - globby "^11.0.0" - read-yaml-file "^1.1.0" - "@manypkg/get-packages@^1.1.3": version "1.1.3" resolved "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz#e184db9bba792fa4693de4658cfb1463ac2c9c47" @@ -4437,13 +4426,13 @@ react-use "^17.2.4" "@roadiehq/backstage-plugin-travis-ci@^1.3.2": - version "1.3.3" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-1.3.3.tgz#317c9280d8dd404794ad5aa057ce457bfd459706" - integrity sha512-f5RhuG681iwiRa9/WvwhV4pI0KHv6oAHlgOXG3/pbOcONN9w9RaQIITgTyc6hpm9MzAkFQWV8qc3yPFcojza+A== + version "1.3.6" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-1.3.6.tgz#6f45a42bdf39aa6baab56f0c4400990238df1625" + integrity sha512-th+2GaOjkPArDUbTBuhhCz/6WL4gcrVRVAsDHzuSrrJqNtsz6kXdBIHhR/58En16BZ2bnHhRzY87uiUDLjAntA== dependencies: "@backstage/catalog-model" "^0.9.7" "@backstage/core-components" "^0.8.0" - "@backstage/core-plugin-api" "^0.4.0" + "@backstage/core-plugin-api" "^0.6.0" "@backstage/plugin-catalog-react" "^0.6.5" "@backstage/theme" "^0.2.9" "@material-ui/core" "^4.11.3" @@ -5071,15 +5060,7 @@ dependencies: "@babel/types" "^7.3.0" -"@types/body-parser@*", "@types/body-parser@^1.19.0": - version "1.19.1" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c" - integrity sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/body-parser@1.19.2": +"@types/body-parser@*", "@types/body-parser@1.19.2", "@types/body-parser@^1.19.0": version "1.19.2" resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== @@ -5395,17 +5376,7 @@ "@types/express" "*" "@types/xml2js" "*" -"@types/express@*", "@types/express@^4.17.6": - version "4.17.12" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz#4bc1bf3cd0cfe6d3f6f2853648b40db7d54de350" - integrity sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/express@4.17.13": +"@types/express@*", "@types/express@4.17.13", "@types/express@^4.17.6": version "4.17.13" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== @@ -5533,6 +5504,13 @@ "@types/through" "*" rxjs "^7.2.0" +"@types/is-ci@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/is-ci/-/is-ci-3.0.0.tgz#7e8910af6857601315592436f030aaa3ed9783c3" + integrity sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ== + dependencies: + ci-info "^3.1.0" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.1" resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -5822,9 +5800,9 @@ "@types/passport-oauth2" "*" "@types/passport-google-oauth20@^2.0.3": - version "2.0.7" - resolved "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.7.tgz#0d97b7a886a0c0d2158682145cd949b29f8efe86" - integrity sha512-0HPVSqDmOWk5fRLb+bqGal+6iWsERiEco/Mli77yy5NEy22IfkoRoqZTSZ8UtXDWY9DCZlpS1Jqq56iWx2torw== + version "2.0.11" + resolved "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.11.tgz#271ec71de3030a3e1c004b24e633e4b298ccba97" + integrity sha512-9XMT1GfwhZL7UQEiCepLef55RNPHkbrCtsU7rsWPTEOsmu5qVIW8nSemtB4p+P24CuOhA+IKkv8LsPThYghGww== dependencies: "@types/express" "*" "@types/passport" "*" @@ -6830,13 +6808,6 @@ anafanafo@2.0.0: dependencies: char-width-table-consumer "^1.0.0" -ansi-align@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" - integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= - dependencies: - string-width "^2.0.0" - ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" @@ -7841,19 +7812,6 @@ bottleneck@^2.15.3: resolved "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== -boxen@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" - integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== - dependencies: - ansi-align "^2.0.0" - camelcase "^4.0.0" - chalk "^2.0.1" - cli-boxes "^1.0.0" - string-width "^2.0.0" - term-size "^1.2.0" - widest-line "^2.0.0" - boxen@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -8098,11 +8056,6 @@ bytes@3.0.0: resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - bytes@3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" @@ -8251,11 +8204,6 @@ camelcase@^3.0.0: resolved "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= -camelcase@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -8328,7 +8276,7 @@ chainsaw@~0.1.0: dependencies: traverse ">=0.3.0 <0.4" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -8500,10 +8448,10 @@ ci-info@^2.0.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -ci-info@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.1.1.tgz#9a32fcefdf7bcdb6f0a7e1c0f8098ec57897b80a" - integrity sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ== +ci-info@^3.1.0, ci-info@^3.2.0: + version "3.3.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -8557,11 +8505,6 @@ clean-stack@^2.0.0: resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-boxes@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= - cli-boxes@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" @@ -9431,7 +9374,7 @@ cross-fetch@^3.0.4, cross-fetch@^3.0.6, cross-fetch@^3.1.3, cross-fetch@^3.1.4: dependencies: node-fetch "2.6.7" -cross-spawn@^5.0.1, cross-spawn@^5.1.0: +cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= @@ -10728,20 +10671,20 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -"embedded-techdocs-app@file:packages/embedded-techdocs-app": - version "0.2.61" +"techdocs-cli-embedded-app@file:packages/techdocs-cli-embedded-app": + version "0.2.62" dependencies: - "@backstage/app-defaults" "^0.1.5" + "@backstage/app-defaults" "^0.1.6" "@backstage/catalog-model" "^0.9.10" - "@backstage/cli" "^0.13.0" + "@backstage/cli" "^0.13.1" "@backstage/config" "^0.1.13" - "@backstage/core-app-api" "^0.5.1" - "@backstage/core-components" "^0.8.6" + "@backstage/core-app-api" "^0.5.2" + "@backstage/core-components" "^0.8.7" "@backstage/core-plugin-api" "^0.6.0" - "@backstage/integration-react" "^0.1.19" - "@backstage/plugin-catalog" "^0.7.10" - "@backstage/plugin-techdocs" "^0.13.1" - "@backstage/test-utils" "^0.2.3" + "@backstage/integration-react" "^0.1.20" + "@backstage/plugin-catalog" "^0.7.11" + "@backstage/plugin-techdocs" "^0.13.2" + "@backstage/test-utils" "^0.2.4" "@backstage/theme" "^0.2.14" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -11526,52 +11469,54 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: safe-buffer "^5.1.1" "example-app@file:packages/app": - version "0.2.62" + version "0.2.63" dependencies: - "@backstage/app-defaults" "^0.1.5" + "@backstage/app-defaults" "^0.1.6" "@backstage/catalog-model" "^0.9.10" - "@backstage/cli" "^0.13.0" - "@backstage/core-app-api" "^0.5.1" - "@backstage/core-components" "^0.8.6" + "@backstage/cli" "^0.13.1" + "@backstage/core-app-api" "^0.5.2" + "@backstage/core-components" "^0.8.7" "@backstage/core-plugin-api" "^0.6.0" - "@backstage/integration-react" "^0.1.19" - "@backstage/plugin-airbrake" "^0.1.1" - "@backstage/plugin-apache-airflow" "^0.1.4" - "@backstage/plugin-api-docs" "^0.7.0" - "@backstage/plugin-azure-devops" "^0.1.12" - "@backstage/plugin-badges" "^0.2.20" - "@backstage/plugin-catalog" "^0.7.10" - "@backstage/plugin-catalog-graph" "^0.2.8" - "@backstage/plugin-catalog-import" "^0.7.10" - "@backstage/plugin-catalog-react" "^0.6.12" - "@backstage/plugin-circleci" "^0.2.35" - "@backstage/plugin-cloudbuild" "^0.2.33" - "@backstage/plugin-code-coverage" "^0.1.23" - "@backstage/plugin-cost-insights" "^0.11.18" - "@backstage/plugin-explore" "^0.3.27" - "@backstage/plugin-gcp-projects" "^0.3.15" - "@backstage/plugin-github-actions" "^0.4.33" - "@backstage/plugin-gocd" "^0.1.2" - "@backstage/plugin-graphiql" "^0.2.28" - "@backstage/plugin-home" "^0.4.12" - "@backstage/plugin-jenkins" "^0.5.18" - "@backstage/plugin-kafka" "^0.2.26" - "@backstage/plugin-kubernetes" "^0.5.5" - "@backstage/plugin-lighthouse" "^0.2.35" - "@backstage/plugin-newrelic" "^0.3.14" - "@backstage/plugin-newrelic-dashboard" "^0.1.4" - "@backstage/plugin-org" "^0.4.0" - "@backstage/plugin-pagerduty" "0.3.23" - "@backstage/plugin-rollbar" "^0.3.24" - "@backstage/plugin-scaffolder" "^0.12.0" - "@backstage/plugin-search" "^0.6.0" - "@backstage/plugin-sentry" "^0.3.34" - "@backstage/plugin-shortcuts" "^0.1.20" - "@backstage/plugin-tech-insights" "^0.1.6" - "@backstage/plugin-tech-radar" "^0.5.3" - "@backstage/plugin-techdocs" "^0.13.1" - "@backstage/plugin-todo" "^0.1.20" - "@backstage/plugin-user-settings" "^0.3.17" + "@backstage/integration-react" "^0.1.20" + "@backstage/plugin-airbrake" "^0.1.2" + "@backstage/plugin-apache-airflow" "^0.1.5" + "@backstage/plugin-api-docs" "^0.7.1" + "@backstage/plugin-azure-devops" "^0.1.13" + "@backstage/plugin-badges" "^0.2.21" + "@backstage/plugin-catalog" "^0.7.11" + "@backstage/plugin-catalog-common" "^0.1.2" + "@backstage/plugin-catalog-graph" "^0.2.9" + "@backstage/plugin-catalog-import" "^0.8.0" + "@backstage/plugin-catalog-react" "^0.6.13" + "@backstage/plugin-circleci" "^0.2.36" + "@backstage/plugin-cloudbuild" "^0.2.34" + "@backstage/plugin-code-coverage" "^0.1.24" + "@backstage/plugin-cost-insights" "^0.11.19" + "@backstage/plugin-explore" "^0.3.28" + "@backstage/plugin-gcp-projects" "^0.3.16" + "@backstage/plugin-github-actions" "^0.4.34" + "@backstage/plugin-gocd" "^0.1.3" + "@backstage/plugin-graphiql" "^0.2.29" + "@backstage/plugin-home" "^0.4.13" + "@backstage/plugin-jenkins" "^0.5.19" + "@backstage/plugin-kafka" "^0.2.27" + "@backstage/plugin-kubernetes" "^0.5.6" + "@backstage/plugin-lighthouse" "^0.2.36" + "@backstage/plugin-newrelic" "^0.3.15" + "@backstage/plugin-newrelic-dashboard" "^0.1.5" + "@backstage/plugin-org" "^0.4.1" + "@backstage/plugin-pagerduty" "0.3.24" + "@backstage/plugin-permission-react" "^0.3.0" + "@backstage/plugin-rollbar" "^0.3.25" + "@backstage/plugin-scaffolder" "^0.12.1" + "@backstage/plugin-search" "^0.6.1" + "@backstage/plugin-sentry" "^0.3.35" + "@backstage/plugin-shortcuts" "^0.1.21" + "@backstage/plugin-tech-insights" "^0.1.7" + "@backstage/plugin-tech-radar" "^0.5.4" + "@backstage/plugin-techdocs" "^0.13.2" + "@backstage/plugin-todo" "^0.1.21" + "@backstage/plugin-user-settings" "^0.3.18" "@backstage/search-common" "^0.2.2" "@backstage/theme" "^0.2.14" "@material-ui/core" "^4.12.2" @@ -11642,19 +11587,6 @@ execa@5.1.1, execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -12598,11 +12530,6 @@ get-stdin@^8.0.0: resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -13048,9 +12975,9 @@ graphql-sse@^1.0.1: integrity sha512-y2mVBN2KwNrzxX2KBncQ6kzc6JWvecxuBernrl0j65hsr6MAS3+Yn8PTFSOgRmtolxugepxveyZVQEuaNEbw3w== graphql-tag@^2.11.0: - version "2.12.4" - resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.4.tgz#d34066688a4f09e72d6f4663c74211e9b4b7c4bf" - integrity sha512-VV1U4O+9x99EkNpNmCUV5RZwq6MnK4+pGbRYWG+lA/m3uo7TSqJF81OkcOP148gFP6fzdl7JWYBrwWVTS9jXww== + version "2.12.6" + resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" + integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== dependencies: tslib "^2.1.0" @@ -13436,17 +13363,6 @@ http-deceiver@^1.2.7: resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= -http-errors@1.7.3: - version "1.7.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-errors@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" @@ -13672,9 +13588,9 @@ ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0: integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== immer@^9.0.1, immer@^9.0.7: - version "9.0.7" - resolved "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075" - integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA== + version "9.0.12" + resolved "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== immutable@^3.x.x: version "3.8.2" @@ -13829,7 +13745,7 @@ inline-style-prefixer@^6.0.0: dependencies: css-in-js-utils "^2.0.0" -inquirer@^7.0.4, inquirer@^7.3.3: +inquirer@^7.3.3: version "7.3.3" resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== @@ -13848,27 +13764,7 @@ inquirer@^7.0.4, inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^8.0.0: - version "8.1.5" - resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.1.5.tgz#2dc5159203c826d654915b5fe6990fd17f54a150" - integrity sha512-G6/9xUqmt/r+UvufSyrPpt84NYwhKZ9jLsgMbQzlx804XErNupor8WQdBnBRrXmBfTPpuwf1sV+ss2ovjgdXIg== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.1" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.21" - mute-stream "0.0.8" - ora "^5.4.1" - run-async "^2.4.0" - rxjs "^7.2.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -inquirer@^8.1.1, inquirer@^8.2.0: +inquirer@^8.0.0, inquirer@^8.1.1, inquirer@^8.2.0: version "8.2.0" resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== @@ -14061,12 +13957,12 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-ci@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" - integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ== +is-ci@^3.0.0, is-ci@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== dependencies: - ci-info "^3.1.1" + ci-info "^3.2.0" is-core-module@^2.1.0, is-core-module@^2.2.0: version "2.8.0" @@ -15721,9 +15617,9 @@ keyv@^3.0.0: json-buffer "3.0.0" keyv@^4.0.0, keyv@^4.0.3: - version "4.0.5" - resolved "https://registry.npmjs.org/keyv/-/keyv-4.0.5.tgz#bb12b467aba372fab2a44d4420c00d3c4ebd484c" - integrity sha512-531pkGLqV3BMg0eDqqJFI0R1mkK1Nm5xIP2mM6keP5P8WfFtCkg2IOwplTUmlGoTgIg9yQYZ/kdihhz89XH3vA== + version "4.1.0" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.1.0.tgz#8ab5ca4ae6a34e05c629531d9a7f871575af0d5b" + integrity sha512-YsY3wr6HabE11/sscee+3nZ03XjvkrPWGouAmJFBdZoK92wiOlJCzI5/sDEIKdJhdhHO144ei45U9gXfbu14Uw== dependencies: json-buffer "3.0.1" @@ -20177,7 +20073,7 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.2: +raw-body@2.4.2, raw-body@^2.4.1: version "2.4.2" resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== @@ -20187,16 +20083,6 @@ raw-body@2.4.2: iconv-lite "0.4.24" unpipe "1.0.0" -raw-body@^2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" - integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== - dependencies: - bytes "3.1.0" - http-errors "1.7.3" - iconv-lite "0.4.24" - unpipe "1.0.0" - rc-progress@3.2.4: version "3.2.4" resolved "https://registry.npmjs.org/rc-progress/-/rc-progress-3.2.4.tgz#4036acdae2566438545bc4df2203248babaf7549" @@ -21103,13 +20989,13 @@ replace-ext@^1.0.0: integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== replace-in-file@^6.0.0: - version "6.1.0" - resolved "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.1.0.tgz#9f9ddd7bb70d6ad231d2ad692e1b646e73d06647" - integrity sha512-URzjyF3nucvejuY13HFd7O+Q6tFJRLKGHLYVvSh+LiZj3gFXzSYGnIkQflnJJulCAI2/RTZaZkpOtdVdW0EhQA== + version "6.3.2" + resolved "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.2.tgz#0f19835137177c89932f45df319f3539a019484f" + integrity sha512-Dbt5pXKvFVPL3WAaEB3ZX+95yP0CeAtIPJDwYzHbPP5EAHn+0UoegH/Wg3HKflU9dYBH8UnBC2NvY3P+9EZtTg== dependencies: - chalk "^4.0.0" - glob "^7.1.6" - yargs "^15.3.1" + chalk "^4.1.2" + glob "^7.2.0" + yargs "^17.2.1" replaceall@^0.1.6: version "0.1.6" @@ -21478,14 +21364,7 @@ rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3: dependencies: tslib "^1.9.0" -rxjs@^7.1.0, rxjs@^7.2.0: - version "7.3.0" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.3.0.tgz#39fe4f3461dc1e50be1475b2b85a0a88c1e938c6" - integrity sha512-p2yuGIg9S1epc3vrjKf6iVb3RCaAYjYskkO+jHIaV0IjOPlJop4UnodOoFb2xeNwlguqLYvGw1b1McillYb5Gw== - dependencies: - tslib "~2.1.0" - -rxjs@^7.5.1: +rxjs@^7.1.0, rxjs@^7.2.0, rxjs@^7.5.1: version "7.5.2" resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.5.2.tgz#11e4a3a1dfad85dbf7fb6e33cbba17668497490b" integrity sha512-PwDt186XaL3QN5qXj/H9DGyHhP3/RYYgZZwqBv9Tv8rsAaiwFH1IsJJlcgD37J7UW5a6O67qX0KWKS3/pu0m4w== @@ -21816,11 +21695,6 @@ setprototypeof@1.1.0: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -22558,7 +22432,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -22575,16 +22449,7 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string-width@^4.2.3: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -23010,6 +22875,11 @@ swap-case@^2.0.2: dependencies: tslib "^2.0.3" +swr@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/swr/-/swr-1.1.2.tgz#9f3de2541931fccf03c48f322f1fc935a7551612" + integrity sha512-UsM0eo5T+kRPyWFZtWRx2XR5qzohs/LS4lDC0GCyLpCYFmsfTk28UCVDbOE9+KtoXY4FnwHYiF+ZYEU3hnJ1lQ== + symbol-observable@1.2.0, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -23157,13 +23027,6 @@ temp@^0.8.4: dependencies: rimraf "~2.6.2" -term-size@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" - integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= - dependencies: - execa "^0.7.0" - term-size@^2.1.0: version "2.2.0" resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" @@ -23433,11 +23296,6 @@ toggle-selection@^1.0.6: resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -23623,12 +23481,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@~2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" - integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== - -tslib@^2.3.0: +tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@~2.3.0: version "2.3.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== @@ -24772,13 +24625,6 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" -widest-line@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" - integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== - dependencies: - string-width "^2.1.1" - widest-line@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" @@ -25137,10 +24983,10 @@ yargs@^16.1.1, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.0.0, yargs@^17.0.1, yargs@^17.1.1, yargs@^17.3.0: - version "17.3.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.3.0.tgz#295c4ffd0eef148ef3e48f7a2e0f58d0e4f26b1c" - integrity sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew== +yargs@^17.0.0, yargs@^17.0.1, yargs@^17.1.1, yargs@^17.2.1, yargs@^17.3.0: + version "17.3.1" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" + integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== dependencies: cliui "^7.0.2" escalade "^3.1.1"