diff --git a/.changeset/forty-students-brake.md b/.changeset/forty-students-brake.md deleted file mode 100644 index 8be76e59d2..0000000000 --- a/.changeset/forty-students-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix `catalog:write` on windows systems diff --git a/.changeset/fuzzy-bottles-greet.md b/.changeset/fuzzy-bottles-greet.md deleted file mode 100644 index d54a4300ae..0000000000 --- a/.changeset/fuzzy-bottles-greet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -Reintroduce export of `defaultConfigLoader`. diff --git a/.changeset/nasty-crews-boil.md b/.changeset/nasty-crews-boil.md deleted file mode 100644 index 5b4108c972..0000000000 --- a/.changeset/nasty-crews-boil.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -Replaced moment and dayjs with Luxon diff --git a/.changeset/thirty-turtles-carry.md b/.changeset/thirty-turtles-carry.md deleted file mode 100644 index 96b0495561..0000000000 --- a/.changeset/thirty-turtles-carry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Show better error message when configs defined under auth.providers. are undefined. diff --git a/.changeset/two-pots-joke.md b/.changeset/two-pots-joke.md deleted file mode 100644 index 7a97777524..0000000000 --- a/.changeset/two-pots-joke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Get rid of flex console warning for IconLink diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index dcad0952f1..846e050c49 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -57,6 +57,7 @@ deadnaming debounce Debounce declaratively +deduplicated deps destructured dev @@ -121,6 +122,7 @@ Knex kubectl kubernetes kubernetes +ldap learnings Leasot lerna @@ -272,6 +274,7 @@ transpilation transpiled truthy ui +unbreak unmanaged unregister unregistration diff --git a/ADOPTERS.md b/ADOPTERS.md index 723906ba7f..83fbc79eca 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,7 +1,7 @@ | Organization | Contact | Description of Use | | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | +| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | | [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | | [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | | [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | @@ -32,5 +32,7 @@ | [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | | [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | | [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | -| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | +| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | | [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | +| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | +| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | diff --git a/README.md b/README.md index 6dd490e43b..f8b54ad200 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ ## What is Backstage? -[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. +[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. Backstage unifies all your infrastructure tooling, services, and documentation to create a streamlined development environment from end to end. -![service-catalog](https://backstage.io/blog/assets/6/header.png) +![software-catalog](https://backstage.io/blog/assets/6/header.png) Out of the box, Backstage includes: -- [Backstage Service Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) +- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) - [Backstage Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index) for quickly spinning up new projects and standardizing your tooling with your organization’s best practices - [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach - Plus, a growing ecosystem of [open source plugins](https://github.com/backstage/backstage/tree/master/plugins) that further expand Backstage’s customizability and functionality @@ -38,7 +38,7 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how ## Documentation - [Main documentation](https://backstage.io/docs) -- [Service Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) +- [Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) - [Architecture](https://backstage.io/docs/overview/architecture-overview) ([Decisions](https://backstage.io/docs/architecture-decisions/adrs-overview)) - [Designing for Backstage](https://backstage.io/docs/dls/design) - [Storybook - UI components](https://backstage.io/storybook) diff --git a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md index 841e13dd8b..8a4d6fb273 100644 --- a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md +++ b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md @@ -26,6 +26,7 @@ import 'global-agent/bootstrap'; ```sh export GLOBAL_AGENT_HTTP_PROXY=$HTTP_PROXY +export GLOBAL_AGENT_NO_PROXY=$NO_PROXY yarn start ``` diff --git a/docs/assets/software-catalog/service-catalog-home.png b/docs/assets/software-catalog/software-catalog-home.png similarity index 100% rename from docs/assets/software-catalog/service-catalog-home.png rename to docs/assets/software-catalog/software-catalog-home.png diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 095a4989d8..e542b02ebf 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -59,16 +59,17 @@ Once the host build is complete, we are ready to build our image. The following FROM node:14-buster-slim WORKDIR /app - # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. # The skeleton contains the package.json of each package in the monorepo, # and along with yarn.lock and the root package.json, that's enough to run yarn install. -ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" # Then copy the rest of the backend bundle, along with any other files we might want. -ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ +COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ +RUN tar xzf bundle.tar.gz && rm bundle.tar.gz CMD ["node", "packages/backend", "--config", "app-config.yaml"] ``` diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 45ef28ea2f..068a17249c 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -68,7 +68,7 @@ The base URL to the Kubernetes control plane. Can be found by using the ##### `clusters.\*.name` A name to represent this cluster, this must be unique within the `clusters` -array. Users will see this value in the Service Catalog Kubernetes plugin. +array. Users will see this value in the Software Catalog Kubernetes plugin. ##### `clusters.\*.authProvider` @@ -195,7 +195,7 @@ annotations: #### Labeling Kubernetes components -In order for Kubernetes components to show up in the service catalog as a part +In order for Kubernetes components to show up in the software catalog as a part of an entity, Kubernetes components themselves can have the following label: ```yaml diff --git a/docs/features/kubernetes/index.md b/docs/features/kubernetes/index.md index 26794eca10..159370fbf4 100644 --- a/docs/features/kubernetes/index.md +++ b/docs/features/kubernetes/index.md @@ -2,7 +2,7 @@ id: overview title: Kubernetes sidebar_label: Overview -description: Monitoring Kubernetes based services with the service catalog +description: Monitoring Kubernetes based services with the software catalog --- Kubernetes in Backstage is a tool that's designed around the needs of service diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md index 2189b7d799..a502c2aee5 100644 --- a/docs/features/software-catalog/index.md +++ b/docs/features/software-catalog/index.md @@ -1,29 +1,29 @@ --- id: software-catalog-overview -title: Backstage Service Catalog (alpha) +title: Backstage Software Catalog (alpha) sidebar_label: Overview # prettier-ignore -description: The Backstage Service Catalog — actually, a software catalog, since it includes more than just services +description: The Backstage Software Catalog --- -## What is a Service Catalog? +## What is a Software Catalog? -The Backstage Service Catalog — actually, a software catalog, since it includes +The Backstage Software Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata YAML files](descriptor-format.md) stored together with the code, which are then harvested and visualized in Backstage. -![service-catalog](https://backstage.io/blog/assets/6/header.png) +![software-catalog](https://backstage.io/blog/assets/6/header.png) ## How it works -Backstage and the Backstage Service Catalog make it easy for one team to manage +Backstage and the Backstage Software Catalog make it easy for one team to manage 10 services — and makes it possible for your company to manage thousands of them. -More specifically, the Service Catalog enables two main use-cases: +More specifically, the Software Catalog enables two main use-cases: 1. Helping teams manage and maintain the software they own. Teams get a uniform view of all their software; services, libraries, websites, ML models — you @@ -37,11 +37,11 @@ The Software Catalog is available to browse at `/catalog`. If you've followed [Getting Started with Backstage](../../getting-started), you should be able to browse the catalog at `http://localhost:3000`. -![](../../assets/software-catalog/service-catalog-home.png) +![](../../assets/software-catalog/software-catalog-home.png) ## Adding components to the catalog -The source of truth for the components in your service catalog are +The source of truth for the components in your software catalog are [metadata YAML files](descriptor-format.md) stored in source control (GitHub, GitHub Enterprise, GitLab, ...). @@ -104,11 +104,11 @@ them, and do so using their normal Git workflow. ![](../../assets/software-catalog/bsc-edit.png) Once the change has been merged, Backstage will automatically show the updated -metadata in the service catalog after a short while. +metadata in the software catalog after a short while. ## Finding software in the catalog -By default the service catalog shows components owned by the team of the logged +By default the software catalog shows components owned by the team of the logged in user. But you can also switch to _All_ to see all the components across your company's software ecosystem. Basic inline _search_ and _column filtering_ makes it easy to browse a big set of components. @@ -124,7 +124,7 @@ _starring_ of components: ## Integrated tooling through plugins -The service catalog is a great way to organize the infrastructure tools you use +The software catalog is a great way to organize the infrastructure tools you use to manage the software. This is how Backstage creates one developer portal for all your tools. Rather than asking teams to jump between different infrastructure UIs (and incurring additional cognitive overhead each time they diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index ecb7d11be9..d185558949 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -83,7 +83,7 @@ spec: [Template Entity](../software-catalog/descriptor-format.md#kind-template) contains more information about the required fields. -Once we have a `template.yaml` ready, we can then add it to the service catalog +Once we have a `template.yaml` ready, we can then add it to the software catalog for use by the scaffolder. You can add the template files to the catalog through diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 8481e64eac..529376eb89 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -4,8 +4,8 @@ title: Writing Templates description: Details around creating your own custom Software Templates --- -Templates are stored in the **Service Catalog** under a kind `Template`. You can -create your own templates with a small `yaml` definition which describes the +Templates are stored in the **Software Catalog** under a kind `Template`. You +can create your own templates with a small `yaml` definition which describes the template and it's metadata, along with some input variables that your template will need, and then a list of actions which are then executed by the scaffolding service. diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 6c64e83a76..3f3c92d0de 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -78,11 +78,6 @@ the repository. The archive does not have any git history attached to it. Also it is a compressed file. Hence the file size is significantly smaller than how much data git clone has to transfer. -Caveat: Currently TechDocs sites built using URL Reader will be cached for 30 -minutes which means they will not be re-built if new changes are made within 30 -minutes. This cache invalidation will be replaced by commit timestamp based -implementation very soon. - ## How to use a custom TechDocs home page? ### 1st way: TechDocsCustomHome with a custom configuration diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index 24a4335b8b..836601971c 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -14,19 +14,25 @@ entities that mirror your org setup. ## Installation -The processor that performs the import, `LdapOrgReaderProcessor`, comes -installed with the default setup of Backstage. +1. The processor is not installed by default, therefore you have to add a + dependency to `@backstage/plugin-catalog-backend-module-ldap` to your backend + package. -If you replace the set of processors in your installation using that facility of -the catalog builder class, you can import and add it as follows. +```bash +# From your Backstage root directory +cd packages/backend +yarn add @backstage/plugin-catalog-backend-module-ldap +``` -```ts -// Typically in packages/backend/src/plugins/catalog.ts -import { LdapOrgReaderProcessor } from '@backstage/plugin-catalog-backend'; +2. The `LdapOrgReaderProcessor` is not registered by default, so you have to + register it in the catalog plugin: -builder.replaceProcessors( - LdapOrgReaderProcessor.fromConfig(config, { logger }), - // ... +```typescript +// packages/backend/src/plugins/catalog.ts +builder.addProcessor( + LdapOrgReaderProcessor.fromConfig(config, { + logger, + }), ); ``` diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 00d83b43ed..7ea892840c 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -25,7 +25,7 @@ different ways. The following diagram shows how Backstage might look when deployed inside a company which uses the Tech Radar plugin, the Lighthouse plugin, the CircleCI -plugin and the service catalog. +plugin and the software catalog. There are 3 main components in this architecture: @@ -142,7 +142,7 @@ Its architecture looks like this: ![lighthouse plugin backed to microservice and database](../assets/architecture-overview/lighthouse-plugin-architecture.png) -The service catalog in Backstage is another example of a service backed plugin. +The software catalog in Backstage is another example of a service backed plugin. It retrieves a list of services, or "entities", from the Backstage Backend service and renders them in a table for the user. diff --git a/docs/overview/background.md b/docs/overview/background.md index 0c22396e72..00fb5107ea 100644 --- a/docs/overview/background.md +++ b/docs/overview/background.md @@ -22,8 +22,8 @@ Our idea was to centralize and simplify end-to-end software development with an abstraction layer that sits on top of all of our infrastructure and developer tooling. That’s Backstage. -It’s a developer portal powered by a centralized service catalog — with a plugin -architecture that makes it endlessly extensible and customizable. +It’s a developer portal powered by a centralized software catalog — with a +plugin architecture that makes it endlessly extensible and customizable. Manage all your services, software, tooling, and testing in Backstage. Start building a new microservice using an automated template in Backstage. Create, diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index a736126324..0013c99c6b 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -22,7 +22,7 @@ We have divided the project into three high-level _phases_: [UX patterns and components](https://backstage.io/storybook) help ensure a consistent experience between tools. -- 🐢 **Phase 2:** Service Catalog +- 🐢 **Phase 2:** Software Catalog ([alpha released](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)) - With a single catalog, Backstage makes it easy for a team to manage ten services — and makes it possible for your company to manage thousands of them. @@ -120,13 +120,13 @@ Chances are that someone will jump in and help build it. - [TechDocs v1](https://backstage.io/blog/2020/09/08/announcing-tech-docs) - [Plugin marketplace](https://backstage.io/plugins) - [Improved and move documentation to backstage.io](https://backstage.io/docs/overview/what-is-backstage) -- [Backstage Service Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) +- [Backstage Software Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) - [Backstage Software Templates (alpha)](https://backstage.io/blog/2020/08/05/announcing-backstage-software-templates) - [Make it possible to add custom auth providers](https://backstage.io/blog/2020/07/01/how-to-enable-authentication-in-backstage-using-passport) - [TechDocs v0](https://github.com/backstage/backstage/milestone/15) - CI plugins: CircleCI, Jenkins, GitHub Actions and TravisCI - [Service API documentation](https://github.com/backstage/backstage/pull/1737) -- Backstage Service Catalog can read from: GitHub, GitLab, +- Backstage Software Catalog can read from: GitHub, GitLab, [Bitbucket](https://github.com/backstage/backstage/pull/1938) - Support auth providers: Google, Okta, GitHub, GitLab, [auth0](https://github.com/backstage/backstage/pull/1611), diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md index ec824a1e11..1d9310541a 100644 --- a/docs/overview/what-is-backstage.md +++ b/docs/overview/what-is-backstage.md @@ -2,13 +2,13 @@ id: what-is-backstage title: What is Backstage? # prettier-ignore -description: Backstage is an open platform for building developer portals. Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure +description: Backstage is an open platform for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure --- -![service-catalog](https://backstage.io/blog/assets/6/header.png) +![software-catalog](https://backstage.io/blog/assets/6/header.png) [Backstage](https://backstage.io/) is an open platform for building developer -portals. Powered by a centralized service catalog, Backstage restores order to +portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. @@ -17,7 +17,7 @@ to create a streamlined development environment from end to end. Out of the box, Backstage includes: -- [Backstage Service Catalog](../features/software-catalog/index.md) for +- [Backstage Software Catalog](../features/software-catalog/index.md) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 32d523b2f5..a582556311 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -28,9 +28,9 @@ This helps the community know what plugins are in development. You can also use this process if you have an idea for a good plugin but you hope that someone else will pick up the work. -## Integrate into the Service Catalog +## Integrate into the Software Catalog If your plugin isn't supposed to live as a standalone page, but rather needs to -be presented as a part of a Service Catalog (e.g. a separate tab or a card on an -"Overview" tab), then check out -[the instruction](integrating-plugin-into-service-catalog.md) on how to do it. +be presented as a part of a Software Catalog (e.g. a separate tab or a card on +an "Overview" tab), then check out +[the instruction](integrating-plugin-into-software-catalog.md) on how to do it. diff --git a/docs/plugins/integrating-plugin-into-service-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md similarity index 95% rename from docs/plugins/integrating-plugin-into-service-catalog.md rename to docs/plugins/integrating-plugin-into-software-catalog.md index 047b6f14e6..ae7c3189da 100644 --- a/docs/plugins/integrating-plugin-into-service-catalog.md +++ b/docs/plugins/integrating-plugin-into-software-catalog.md @@ -1,7 +1,7 @@ --- -id: integrating-plugin-into-service-catalog -title: Integrate into the Service Catalog -description: How to integrate a plugin into service catalog +id: integrating-plugin-into-software-catalog +title: Integrate into the Software Catalog +description: How to integrate a plugin into software catalog --- > This is an advanced use case and currently is an experimental feature. Expect diff --git a/microsite/core/Footer.js b/microsite/core/Footer.js index ec08216367..8e69b7ff6f 100644 --- a/microsite/core/Footer.js +++ b/microsite/core/Footer.js @@ -33,7 +33,7 @@ class Footer extends React.Component { - Service Catalog + Software Catalog Create a Plugin Designing for Backstage diff --git a/microsite/data/plugins/backstage-service-catalog.yaml b/microsite/data/plugins/backstage-software-catalog.yaml similarity index 79% rename from microsite/data/plugins/backstage-service-catalog.yaml rename to microsite/data/plugins/backstage-software-catalog.yaml index 1a788078a5..ae16550297 100644 --- a/microsite/data/plugins/backstage-service-catalog.yaml +++ b/microsite/data/plugins/backstage-software-catalog.yaml @@ -1,10 +1,10 @@ --- -title: Backstage Service Catalog +title: Backstage Software Catalog author: Spotify authorUrl: https://github.com/spotify category: Core Feature description: Manage all your services and software components, all in one place. documentation: https://backstage.io/docs/features/software-catalog/software-catalog-overview -iconUrl: img/backstage-service-catalog.svg +iconUrl: img/backstage-software-catalog.svg npmPackageName: '@backstage/plugin-catalog' order: 1 diff --git a/microsite/package.json b/microsite/package.json index 1894e37d84..eee549fb70 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -19,7 +19,7 @@ "@spotify/prettier-config": "^10.0.0", "docusaurus": "^2.0.0-alpha.70", "js-yaml": "^4.1.0", - "prettier": "^2.3.1", + "prettier": "^2.3.2", "yarn-lock-check": "^1.0.5" }, "prettier": "@spotify/prettier-config" diff --git a/microsite/pages/en/demos.js b/microsite/pages/en/demos.js index e3943d2905..7d97641815 100644 --- a/microsite/pages/en/demos.js +++ b/microsite/pages/en/demos.js @@ -42,7 +42,7 @@ const Background = props => { To explore the UI and basic features of Backstage firsthand, go to: demo.backstage.io. (Tip: click “All” to view all the example components in the - service catalog.) + software catalog.) diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js index 45d87daa1d..d7325bb234 100644 --- a/microsite/pages/en/index.js +++ b/microsite/pages/en/index.js @@ -27,7 +27,7 @@ class Index extends React.Component { An open platform for building developer portals - Powered by a centralized service catalog, Backstage restores + Powered by a centralized software catalog, Backstage restores order to your infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. @@ -102,10 +102,10 @@ class Index extends React.Component { {' '} - Backstage Service Catalog{' '} + Backstage Software Catalog{' '} - Learn more about the service catalog + Learn more about the software catalog ; @@ -294,6 +297,9 @@ export function requestLoggingHandler(logger?: Logger): RequestHandler; // @public export function resolvePackagePath(name: string, ...paths: string[]): string; +// @public +export function resolveSafeChildPath(base: string, path: string): string; + // @public (undocumented) export type RunContainerOptions = { imageName: string; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index fdab4b4504..d395b8d557 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.8.3", + "version": "0.8.4", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -29,11 +29,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli-common": "^0.1.1", + "@backstage/cli-common": "^0.1.2", "@backstage/config": "^0.1.5", "@backstage/config-loader": "^0.6.4", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.6", + "@backstage/integration": "^0.5.7", "@google-cloud/storage": "^5.8.0", "@octokit/rest": "^18.5.3", "@types/cors": "^2.8.6", @@ -76,7 +76,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.7.1", + "@backstage/cli": "^0.7.3", "@backstage/test-utils": "^0.1.12", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", diff --git a/packages/backend-common/src/paths.ts b/packages/backend-common/src/paths.ts index efe8e027c8..ffe99fe762 100644 --- a/packages/backend-common/src/paths.ts +++ b/packages/backend-common/src/paths.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { isChildPath } from '@backstage/cli-common'; +import { NotAllowedError } from '@backstage/errors'; import { resolve as resolvePath } from 'path'; /** @@ -32,3 +34,27 @@ export function resolvePackagePath(name: string, ...paths: string[]) { return resolvePath(req.resolve(`${name}/package.json`), '..', ...paths); } + +/** + * Resolves a target path from a base path while guaranteeing that the result is + * a path that point to or within the base path. This is useful for resolving + * paths from user input, as it otherwise opens up for vulnerabilities. + * + * @param base The base directory to resolve the path from. + * @param path The target path, relative or absolute + * @returns A path that is guaranteed to point to or within the base path. + */ +export function resolveSafeChildPath(base: string, path: string): string { + const targetPath = resolvePath(base, path); + + if (!isChildPath(base, targetPath)) { + throw new NotAllowedError( + 'Relative path is not allowed to refer to a directory outside its parent', + ); + } + + return targetPath; +} + +// Re-export isChildPath so that backend packages don't need to depend on cli-common +export { isChildPath }; diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.test.ts b/packages/backend-common/src/reading/BitbucketUrlReader.test.ts index d8bcde2397..cc540cb83c 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.test.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.test.ts @@ -142,7 +142,7 @@ describe('BitbucketUrlReader', () => { ), ), rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive?format=tgz&prefix=mock&path=docs', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive', (_, res, ctx) => res( ctx.status(200), @@ -366,7 +366,7 @@ describe('BitbucketUrlReader', () => { beforeEach(() => { worker.use( rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive?format=tgz&prefix=mock&path=docs', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive', (_, res, ctx) => res( ctx.status(200), diff --git a/packages/backend-common/src/reading/GithubUrlReader.test.ts b/packages/backend-common/src/reading/GithubUrlReader.test.ts index 787ef82f60..0b3288e59c 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.test.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.test.ts @@ -118,7 +118,7 @@ describe('GithubUrlReader', () => { worker.use( rest.get( - 'https://ghe.github.com/api/v3/repos/backstage/mock/tree/contents/?ref=main', + 'https://ghe.github.com/api/v3/repos/backstage/mock/tree/contents/', (req, res, ctx) => { expect(req.headers.get('authorization')).toBe( mockHeaders.Authorization, diff --git a/packages/backend-common/src/reading/GitlabUrlReader.test.ts b/packages/backend-common/src/reading/GitlabUrlReader.test.ts index a76b898aaf..9c2e428239 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.test.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.test.ts @@ -182,7 +182,7 @@ describe('GitlabUrlReader', () => { describe('readTree', () => { const archiveBuffer = fs.readFileSync( - path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.zip'), + path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.tar.gz'), ); const projectGitlabApiResponse = { @@ -205,7 +205,7 @@ describe('GitlabUrlReader', () => { beforeEach(() => { worker.use( rest.get( - 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive.zip?sha=main', + 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive', (_, res, ctx) => res( ctx.status(200), @@ -283,7 +283,7 @@ describe('GitlabUrlReader', () => { }, ), rest.get( - 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/archive.zip?sha=main', + 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/archive', (_, res, ctx) => res( ctx.status(200), @@ -306,8 +306,8 @@ describe('GitlabUrlReader', () => { const files = await response.files(); expect(files.length).toBe(2); - const mkDocsFile = await files[0].content(); - const indexMarkdownFile = await files[1].content(); + const indexMarkdownFile = await files[0].content(); + const mkDocsFile = await files[1].content(); expect(mkDocsFile.toString()).toBe('site_name: Test\n'); expect(indexMarkdownFile.toString()).toBe('# Test\n'); @@ -318,7 +318,7 @@ describe('GitlabUrlReader', () => { 'https://gitlab.com/backstage/mock', ); - const dir = await response.dir({ targetDir: '/tmp' }); + const dir = await response.dir({ targetDir: tmpDir }); await expect( fs.readFile(path.join(dir, 'mkdocs.yml'), 'utf8'), @@ -331,7 +331,7 @@ describe('GitlabUrlReader', () => { it('returns the wanted files from hosted gitlab', async () => { worker.use( rest.get( - 'https://gitlab.mycompany.com/backstage/mock/-/archive/main.zip', + 'https://gitlab.mycompany.com/backstage/mock/-/archive/main.tar.gz', (_, res, ctx) => res( ctx.status(200), @@ -375,7 +375,7 @@ describe('GitlabUrlReader', () => { 'https://gitlab.com/backstage/mock/tree/main/docs', ); - const dir = await response.dir({ targetDir: '/tmp' }); + const dir = await response.dir({ targetDir: tmpDir }); await expect( fs.readFile(path.join(dir, 'index.md'), 'utf8'), @@ -454,7 +454,7 @@ describe('GitlabUrlReader', () => { describe('search', () => { const archiveBuffer = fs.readFileSync( - path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.zip'), + path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.tar.gz'), ); const projectGitlabApiResponse = { @@ -471,7 +471,7 @@ describe('GitlabUrlReader', () => { beforeEach(() => { worker.use( rest.get( - 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive.zip?sha=main', + 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive', (_, res, ctx) => res( ctx.status(200), diff --git a/packages/backend-common/src/reading/GitlabUrlReader.ts b/packages/backend-common/src/reading/GitlabUrlReader.ts index 13764a5312..aa215aee47 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.ts @@ -139,7 +139,7 @@ export class GitlabUrlReader implements UrlReader { const archiveGitLabResponse = await fetch( `${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent( full_name, - )}/repository/archive.zip?sha=${branch}`, + )}/repository/archive?sha=${branch}`, getGitLabRequestOptions(this.integration.config), ); if (!archiveGitLabResponse.ok) { @@ -150,7 +150,7 @@ export class GitlabUrlReader implements UrlReader { throw new Error(message); } - return await this.deps.treeResponseFactory.fromZipArchive({ + return await this.deps.treeResponseFactory.fromTarArchive({ stream: (archiveGitLabResponse.body as unknown) as Readable, subpath: filepath, etag: commitSha, diff --git a/packages/backend-common/src/reading/__fixtures__/gitlab-archive.tar.gz b/packages/backend-common/src/reading/__fixtures__/gitlab-archive.tar.gz new file mode 100644 index 0000000000..c078e07e7e Binary files /dev/null and b/packages/backend-common/src/reading/__fixtures__/gitlab-archive.tar.gz differ diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 65d28457d0..9659932635 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,18 @@ # example-backend +## 0.2.35 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.12.4 + - @backstage/backend-common@0.8.4 + - @backstage/plugin-auth-backend@0.3.15 + - @backstage/plugin-catalog-backend@0.11.0 + - @backstage/plugin-techdocs-backend@0.8.5 + - @backstage/catalog-client@0.3.15 + - @backstage/plugin-kafka-backend@0.2.7 + ## 0.2.32 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index f4f304d714..a2c5906696 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.32", + "version": "0.2.35", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -27,24 +27,24 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.8.2", - "@backstage/catalog-client": "^0.3.13", + "@backstage/backend-common": "^0.8.4", + "@backstage/catalog-client": "^0.3.15", "@backstage/catalog-model": "^0.8.2", "@backstage/config": "^0.1.5", "@backstage/plugin-app-backend": "^0.3.13", - "@backstage/plugin-auth-backend": "^0.3.12", + "@backstage/plugin-auth-backend": "^0.3.15", "@backstage/plugin-badges-backend": "^0.1.6", - "@backstage/plugin-catalog-backend": "^0.10.2", + "@backstage/plugin-catalog-backend": "^0.11.0", "@backstage/plugin-code-coverage-backend": "^0.1.6", "@backstage/plugin-graphql-backend": "^0.1.8", "@backstage/plugin-kubernetes-backend": "^0.3.8", - "@backstage/plugin-kafka-backend": "^0.2.6", + "@backstage/plugin-kafka-backend": "^0.2.7", "@backstage/plugin-proxy-backend": "^0.2.9", "@backstage/plugin-rollbar-backend": "^0.1.11", - "@backstage/plugin-scaffolder-backend": "^0.12.0", + "@backstage/plugin-scaffolder-backend": "^0.12.4", "@backstage/plugin-search-backend": "^0.2.0", "@backstage/plugin-search-backend-node": "^0.2.0", - "@backstage/plugin-techdocs-backend": "^0.8.2", + "@backstage/plugin-techdocs-backend": "^0.8.5", "@backstage/plugin-todo-backend": "^0.1.6", "@gitbeaker/node": "^30.2.0", "@octokit/rest": "^18.5.3", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.3", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 830edbb600..2bae1fc11e 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/catalog-client +## 0.3.15 + +### Patch Changes + +- ca080cab8: Don't crash if the entities response doesn't include the entities name and kind + ## 0.3.14 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 24ac72bb67..425e70c20d 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "0.3.14", + "version": "0.3.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,7 +35,7 @@ "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.7.2", + "@backstage/cli": "^0.7.3", "@types/jest": "^26.0.7", "msw": "^0.29.0" }, diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index 6d43af85ed..4e3d95f99f 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -151,6 +151,26 @@ describe('CatalogClient', () => { expect(response.items).toEqual([]); }); + + it('handles field filtered entities', async () => { + server.use( + rest.get(`${mockBaseUrl}/entities`, (_req, res, ctx) => { + return res(ctx.json([{ apiVersion: '1' }, { apiVersion: '2' }])); + }), + ); + + const response = await client.getEntities( + { + fields: ['apiVersion'], + }, + { token }, + ); + + expect(response.items).toEqual([ + { apiVersion: '1' }, + { apiVersion: '2' }, + ]); + }); }); describe('getLocationById', () => { diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 10c282f786..5dfcddde0c 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -92,6 +92,16 @@ export class CatalogClient implements CatalogApi { ); const refCompare = (a: Entity, b: Entity) => { + // in case field filtering is used, these fields might not be part of the response + if ( + a.metadata?.name === undefined || + a.kind === undefined || + b.metadata?.name === undefined || + b.kind === undefined + ) { + return 0; + } + const aRef = stringifyEntityRef(a); const bRef = stringifyEntityRef(b); if (aRef < bRef) { diff --git a/packages/cli-common/CHANGELOG.md b/packages/cli-common/CHANGELOG.md new file mode 100644 index 0000000000..52a247f57a --- /dev/null +++ b/packages/cli-common/CHANGELOG.md @@ -0,0 +1,7 @@ +# @backstage/cli-common + +## 0.1.2 + +### Patch Changes + +- ab5cc376f: Add new `isChildPath` export diff --git a/packages/cli-common/api-report.md b/packages/cli-common/api-report.md index bcdc023c22..7141d27f3a 100644 --- a/packages/cli-common/api-report.md +++ b/packages/cli-common/api-report.md @@ -7,6 +7,9 @@ // @public export function findPaths(searchDir: string): Paths; +// @public +export function isChildPath(base: string, path: string): boolean; + // @public (undocumented) export type Paths = { ownDir: string; diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json index a9fd066ef4..16f10717f9 100644 --- a/packages/cli-common/package.json +++ b/packages/cli-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli-common", "description": "Common functionality used by cli, backend, and create-app", - "version": "0.1.1", + "version": "0.1.2", "private": false, "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/cli-common/src/index.ts b/packages/cli-common/src/index.ts index d36314ba35..1f4f17fe9f 100644 --- a/packages/cli-common/src/index.ts +++ b/packages/cli-common/src/index.ts @@ -15,4 +15,5 @@ */ export { findPaths } from './paths'; +export { isChildPath } from './isChildPath'; export type { Paths } from './paths'; diff --git a/packages/cli-common/src/isChildPath.test.ts b/packages/cli-common/src/isChildPath.test.ts new file mode 100644 index 0000000000..4c0d94f07c --- /dev/null +++ b/packages/cli-common/src/isChildPath.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { posix, win32 } from 'path'; + +describe('isChildPath', () => { + it('should check child posix paths', () => { + jest.isolateModules(() => { + jest.setMock('path', posix); + const { isChildPath } = require('./isChildPath'); + + expect(isChildPath('/', '/')).toBe(true); + expect(isChildPath('/x', '/x')).toBe(true); + expect(isChildPath('/x', '/x/y')).toBe(true); + expect(isChildPath('/x', '/x/x')).toBe(true); + expect(isChildPath('/x', '/x/y/z')).toBe(true); + expect(isChildPath('/x/y', '/x/y/z')).toBe(true); + expect(isChildPath('/x/y/z', '/x/y/z')).toBe(true); + expect(isChildPath('/x/a b c/z', '/x/a b c/z')).toBe(true); + expect(isChildPath('/', '/ yz')).toBe(true); + + expect(isChildPath('/x', '/y')).toBe(false); + expect(isChildPath('/x', '/')).toBe(false); + expect(isChildPath('/x', '/x y')).toBe(false); + expect(isChildPath('/x y', '/x yz')).toBe(false); + expect(isChildPath('/ yz', '/')).toBe(false); + expect(isChildPath('/x', '/')).toBe(false); + + jest.dontMock('path'); + }); + }); + + it('should check child win32 paths', () => { + jest.isolateModules(() => { + jest.setMock('path', win32); + const { isChildPath } = require('./isChildPath'); + + expect(isChildPath('/x', '/x')).toBe(true); + expect(isChildPath('/x', '/x/y')).toBe(true); + expect(isChildPath('/x', '/x/x')).toBe(true); + expect(isChildPath('/x', '/x/y/z')).toBe(true); + expect(isChildPath('/x/y', '/x/y/z')).toBe(true); + expect(isChildPath('/x/y/z', '/x/y/z')).toBe(true); + expect(isChildPath('Z:', 'Z:')).toBe(true); + expect(isChildPath('C:/', 'c:/')).toBe(true); + expect(isChildPath('C:/x', 'C:/x')).toBe(true); + expect(isChildPath('C:/x', 'c:/x')).toBe(true); + expect(isChildPath('C:/x', 'C:/x/y')).toBe(true); + expect(isChildPath('d:/x', 'D:/x/y')).toBe(true); + + expect(isChildPath('/x', '/y')).toBe(false); + expect(isChildPath('/x', '/')).toBe(false); + expect(isChildPath('C:/', 'D:/')).toBe(false); + expect(isChildPath('C:/x', 'D:/x')).toBe(false); + expect(isChildPath('D:/x', 'CD:/x')).toBe(false); + expect(isChildPath('D:/x', 'D:/y')).toBe(false); + + jest.dontMock('path'); + }); + }); +}); diff --git a/packages/cli-common/src/isChildPath.ts b/packages/cli-common/src/isChildPath.ts new file mode 100644 index 0000000000..f48c92db32 --- /dev/null +++ b/packages/cli-common/src/isChildPath.ts @@ -0,0 +1,33 @@ +/* + * 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 { relative, isAbsolute } from 'path'; + +/** + * Checks if path is the same as or a child path of base. + */ +export function isChildPath(base: string, path: string): boolean { + const relativePath = relative(base, path); + if (relativePath === '') { + // The same directory + return true; + } + + const outsideBase = relativePath.startsWith('..'); // not outside base + const differentDrive = isAbsolute(relativePath); // on Windows, this means dir is on a different drive from base. + + return !outsideBase && !differentDrive; +} diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 87b726600b..a8cacedf22 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/cli +## 0.7.3 + +### Patch Changes + +- a93e60fdc: Updated dependencies +- 55f49fcc7: Update dependencies +- ab5cc376f: Use new `isChildPath` export from `@backstage/cli-common` +- Updated dependencies + - @backstage/cli-common@0.1.2 + ## 0.7.2 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index b0e6b021cc..200531a1ec 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.7.2", + "version": "0.7.3", "private": false, "publishConfig": { "access": "public" @@ -30,7 +30,7 @@ "dependencies": { "@babel/core": "^7.4.4", "@babel/plugin-transform-modules-commonjs": "^7.4.4", - "@backstage/cli-common": "^0.1.1", + "@backstage/cli-common": "^0.1.2", "@backstage/config": "^0.1.5", "@backstage/config-loader": "^0.6.4", "@hot-loader/react-dom": "^16.13.0", @@ -40,7 +40,7 @@ "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-json": "^4.0.2", "@rollup/plugin-node-resolve": "^13.0.0", - "@rollup/plugin-yaml": "^2.1.1", + "@rollup/plugin-yaml": "^3.0.0", "@spotify/eslint-config-base": "^9.0.0", "@spotify/eslint-config-react": "^10.0.0", "@spotify/eslint-config-typescript": "^10.0.0", @@ -61,7 +61,7 @@ "chalk": "^4.0.0", "chokidar": "^3.3.1", "commander": "^6.1.0", - "css-loader": "^3.5.3", + "css-loader": "^5.2.6", "dashify": "^2.0.0", "diff": "^5.0.0", "esbuild": "^0.8.56", @@ -118,11 +118,11 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.8.3", + "@backstage/backend-common": "^0.8.4", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", - "@backstage/core-app-api": "^0.1.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@backstage/theme": "^0.2.8", @@ -146,7 +146,7 @@ "del": "^6.0.0", "mock-fs": "^4.13.0", "nodemon": "^2.0.2", - "ts-node": "^9.1.1" + "ts-node": "^10.0.0" }, "files": [ "asset-types", diff --git a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts index 2360078a18..aa6a6b4e2e 100644 --- a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts +++ b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts @@ -16,7 +16,7 @@ import { resolve as resolvePath } from 'path'; import { ResolvePlugin } from 'webpack'; -import { isChildPath } from './paths'; +import { isChildPath } from '@backstage/cli-common'; import { LernaPackage } from './types'; // Enables proper resolution of packages when linking in external packages. diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 89f677109d..75e94af716 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -22,9 +22,10 @@ import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin'; import StartServerPlugin from 'start-server-webpack-plugin'; import webpack from 'webpack'; import nodeExternals from 'webpack-node-externals'; +import { isChildPath } from '@backstage/cli-common'; import { optimization } from './optimization'; import { Config } from '@backstage/config'; -import { BundlingPaths, isChildPath } from './paths'; +import { BundlingPaths } from './paths'; import { transforms } from './transforms'; import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin'; import { BundlingOptions, BackendBundlingOptions, LernaPackage } from './types'; diff --git a/packages/cli/src/lib/bundler/paths.ts b/packages/cli/src/lib/bundler/paths.ts index 01fdcdefe5..d465b3010f 100644 --- a/packages/cli/src/lib/bundler/paths.ts +++ b/packages/cli/src/lib/bundler/paths.ts @@ -15,25 +15,8 @@ */ import fs from 'fs-extra'; -import path from 'path'; import { paths } from '../paths'; -/** - * Checks if dir is the same as or a child of base. - */ -export function isChildPath(base: string, dir: string): boolean { - const relativePath = path.relative(base, dir); - if (relativePath === '') { - // The same directory - return true; - } - - const outsideBase = relativePath.startsWith('..'); // not outside base - const differentDrive = path.isAbsolute(relativePath); // on Windows, this means dir is on a different drive from base. - - return !outsideBase && !differentDrive; -} - export type BundlingPathsOptions = { // bundle entrypoint, e.g. 'src/index' entry: string; diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 3b617f4623..d5291222d8 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/codemods +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@0.1.4 + - @backstage/core-components@0.1.4 + - @backstage/cli-common@0.1.2 + ## 0.1.3 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index d9ea7fb1b7..2b557685bd 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.3", + "version": "0.1.4", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "backstage-codemods": "bin/backstage-codemods" }, "dependencies": { - "@backstage/cli-common": "0.1.1", + "@backstage/cli-common": "0.1.2", "@backstage/core-app-api": "*", "@backstage/core-components": "*", "@backstage/core-plugin-api": "*", @@ -43,7 +43,7 @@ "@types/jscodeshift": "^0.11.0", "@types/node": "^14.14.32", "commander": "^6.1.0", - "ts-node": "^9.1.1" + "ts-node": "^10.0.0" }, "nodemonConfig": { "watch": "./src", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index c96fbb5aba..4f09f231c3 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/core-app-api +## 0.1.4 + +### Patch Changes + +- 62abffee4: Reintroduce export of `defaultConfigLoader`. +- Updated dependencies + - @backstage/core-components@0.1.4 + ## 0.1.3 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 42db08a136..0f6773497b 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "0.1.3", + "version": "0.1.4", "private": false, "publishConfig": { "access": "public", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.2", + "@backstage/core-components": "^0.1.4", "@backstage/config": "^0.1.3", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", @@ -44,7 +44,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.2", + "@backstage/cli": "^0.7.3", "@backstage/test-utils": "^0.1.14", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index db49ad5adb..7f3bc09225 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/core-components +## 0.1.4 + +### Patch Changes + +- f423891ee: Fixed sizing of the System diagram when the rendered graph was wider than the container. +- 3db266fe4: Make `ErrorBoundary` display more helpful information about the error that + occurred. + + The `slackChannel` (optional) prop can now be passed as an object on the form + `{ name: string; href?: string; }` in addition to the old string form. If you + are using the error boundary like + + ```tsx + + + + ``` + + you may like to migrate it to + + ```tsx + const support = { + name: '#support', + href: 'https://slack.com/channels/your-channel', + }; + + + + + ``` + + Also deprecated the prop `slackChannel` on `TabbedCard` and `InfoCard`, while + adding the prop `errorBoundaryProps` to replace it. + +- e8c65b068: Clear the previously selected sign-in provider on failure + ## 0.1.3 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 428c0e9134..1c2e160bd5 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.1.3", + "version": "0.1.4", "private": false, "publishConfig": { "access": "public", @@ -70,8 +70,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/core-app-api": "^0.1.2", - "@backstage/cli": "^0.7.1", + "@backstage/core-app-api": "^0.1.4", + "@backstage/cli": "^0.7.3", "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index 5c0442f906..89f7421d2a 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -262,7 +262,7 @@ export function DependencyGraph({ diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx index c90874fba0..37ae5b2c62 100644 --- a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx +++ b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx @@ -107,6 +107,7 @@ export const WarningPanel = (props: Props) => { } diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx index 1dbeeca8c6..f8adb81a2a 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx @@ -17,7 +17,13 @@ import React from 'react'; import { ErrorBoundary } from './ErrorBoundary'; -import { renderInTestApp, withLogCollector } from '@backstage/test-utils'; +import { + MockErrorApi, + renderInTestApp, + withLogCollector, +} from '@backstage/test-utils'; +import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { errorApiRef } from '@backstage/core-plugin-api'; type BombProps = { shouldThrow?: boolean; @@ -35,28 +41,33 @@ const Bomb = ({ shouldThrow }: BombProps) => { describe('', () => { it('should render error boundary with and without error', async () => { const { error } = await withLogCollector(['error'], async () => { + const apis = ApiRegistry.with(errorApiRef, new MockErrorApi()); const { rerender, queryByRole, getByRole, getByText, } = await renderInTestApp( - - - , + + + + + , ); expect(queryByRole('alert')).not.toBeInTheDocument(); expect(getByText(/working component/i)).toBeInTheDocument(); rerender( - - - , + + + + + , ); expect(getByRole('alert')).toBeInTheDocument(); - expect(getByText(/something went wrong here/i)).toBeInTheDocument(); + expect(getByText(/something went wrong/i)).toBeInTheDocument(); }); expect(error).toEqual([ diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx index 4134808311..b8dfd7ff3a 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx @@ -15,9 +15,16 @@ */ import React, { ComponentClass, Component, ErrorInfo } from 'react'; +import { Button } from '../../components/Button'; +import { ErrorPanel } from '../../components/ErrorPanel'; -type Props = { - slackChannel?: string; +type SlackChannel = { + name: string; + href?: string; +}; + +export type ErrorBoundaryProps = { + slackChannel?: string | SlackChannel; onError?: (error: Error, errorInfo: string) => null; }; @@ -26,28 +33,30 @@ type State = { errorInfo?: ErrorInfo; }; -type EProps = { - error?: Error; - slackChannel?: string; - children?: React.ReactNode; -}; +const SlackLink = (props: { slackChannel?: string | SlackChannel }) => { + const { slackChannel } = props; + + if (!slackChannel) { + return null; + } else if (typeof slackChannel === 'string') { + return <>Please contact {slackChannel} for help.; + } else if (!slackChannel.href) { + return <>Please contact {slackChannel.name} for help.; + } -const Error = ({ slackChannel }: EProps) => { return ( -
- Something went wrong here.{' '} - {slackChannel && <>Please contact {slackChannel} for help.} -
+ ); }; export const ErrorBoundary: ComponentClass< - Props, + ErrorBoundaryProps, State -> = class ErrorBoundary extends Component { - constructor(props: Props) { +> = class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { super(props); - this.state = { error: undefined, errorInfo: undefined, @@ -61,13 +70,17 @@ export const ErrorBoundary: ComponentClass< } render() { - const { slackChannel } = this.props; - const { error, errorInfo } = this.state; + const { slackChannel, children } = this.props; + const { error } = this.state; - if (!errorInfo) { - return this.props.children; + if (!error) { + return children; } - return ; + return ( + + + + ); } }; diff --git a/packages/core-components/src/layout/ErrorBoundary/index.ts b/packages/core-components/src/layout/ErrorBoundary/index.ts index 47c1b2bace..a0640bd5ef 100644 --- a/packages/core-components/src/layout/ErrorBoundary/index.ts +++ b/packages/core-components/src/layout/ErrorBoundary/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ +export type { ErrorBoundaryProps } from './ErrorBoundary'; export { ErrorBoundary } from './ErrorBoundary'; diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index 9a64bfa6f3..763038388b 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -26,7 +26,7 @@ import { makeStyles, } from '@material-ui/core'; import classNames from 'classnames'; -import { ErrorBoundary } from '../ErrorBoundary'; +import { ErrorBoundary, ErrorBoundaryProps } from '../ErrorBoundary'; import { BottomLink, BottomLinkProps } from '../BottomLink'; const useStyles = makeStyles(theme => ({ @@ -95,8 +95,8 @@ export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; * You can custom style an InfoCard with the 'style' (outer container) and 'cardStyle' (inner container) * styles. * - * The InfoCard serves as an error boundary. As a result, if you provide a 'slackChannel' property this - * specifies the channel to display in the error component that is displayed if an error occurs + * The InfoCard serves as an error boundary. As a result, if you provide an 'errorBoundaryProps' property this + * specifies the extra information to display in the error component that is displayed if an error occurs * in any descendent components. * * By default the InfoCard has no custom layout of its children, but is treated as a block element. A @@ -112,7 +112,9 @@ type Props = { subheader?: ReactNode; divider?: boolean; deepLink?: BottomLinkProps; + /** @deprecated Use errorBoundaryProps instead */ slackChannel?: string; + errorBoundaryProps?: ErrorBoundaryProps; variant?: InfoCardVariants; style?: object; cardStyle?: object; @@ -133,7 +135,8 @@ export const InfoCard = ({ subheader, divider = true, deepLink, - slackChannel = '#backstage', + slackChannel, + errorBoundaryProps, variant, children, headerStyle, @@ -169,9 +172,12 @@ export const InfoCard = ({ }); } + const errProps: ErrorBoundaryProps = + errorBoundaryProps || (slackChannel ? { slackChannel } : {}); + return ( - + {title && ( ({ root: { @@ -52,7 +52,9 @@ const BoldHeader = withStyles(theme => ({ }))(CardHeader); type Props = { + /** @deprecated Use errorBoundaryProps instead */ slackChannel?: string; + errorBoundaryProps?: ErrorBoundaryProps; children?: ReactElement[]; onChange?: (event: React.ChangeEvent<{}>, value: number | string) => void; title?: string; @@ -61,7 +63,8 @@ type Props = { }; const TabbedCard = ({ - slackChannel = '#backstage', + slackChannel, + errorBoundaryProps, children, title, deepLink, @@ -87,9 +90,12 @@ const TabbedCard = ({ }); } + const errProps: ErrorBoundaryProps = + errorBoundaryProps || (slackChannel ? { slackChannel } : {}); + return ( - + {title && } ; + // (undocumented) + getAppToken(owner: string, repo?: string): Promise; +} + // @public (undocumented) export class GithubCredentialsProvider { // (undocumented) diff --git a/packages/integration/package.json b/packages/integration/package.json index 076ba70aba..9313906ce1 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "0.5.6", + "version": "0.5.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,7 +37,7 @@ "luxon": "^1.25.0" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.3", "@backstage/config-loader": "^0.6.3", "@backstage/test-utils": "^0.1.13", "@types/jest": "^26.0.7", diff --git a/packages/integration/src/github/GithubCredentialsProvider.test.ts b/packages/integration/src/github/GithubCredentialsProvider.test.ts index 18cad6fb78..85df1bb4e1 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/GithubCredentialsProvider.test.ts @@ -15,6 +15,7 @@ */ const octokit = { + paginate: async (fn: any) => (await fn()).data, apps: { listInstallations: jest.fn(), createInstallationAccessToken: jest.fn(), @@ -53,7 +54,7 @@ describe('GithubCredentialsProvider tests', () => { jest.resetAllMocks(); }); it('create repository specific tokens', async () => { - octokit.apps.listInstallations.mockResolvedValueOnce({ + octokit.apps.listInstallations.mockResolvedValue({ headers: { etag: '123', }, @@ -72,7 +73,6 @@ describe('GithubCredentialsProvider tests', () => { }, ], } as RestEndpointMethodTypes['apps']['listInstallations']['response']); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ data: { @@ -84,12 +84,8 @@ describe('GithubCredentialsProvider tests', () => { const { token, headers, type } = await github.getCredentials({ url: 'https://github.com/backstage/foobar', }); - const { token: accessToken2 } = await github.getCredentials({ - url: 'https://github.com/backstage/foobar', - }); expect(type).toEqual('app'); expect(token).toEqual('secret_token'); - expect(token).toEqual(accessToken2); expect(headers).toEqual({ Authorization: 'Bearer secret_token' }); // fallback to the configured token if no application is matching @@ -107,7 +103,7 @@ describe('GithubCredentialsProvider tests', () => { }); it('creates tokens for an organization', async () => { - octokit.apps.listInstallations.mockResolvedValueOnce({ + octokit.apps.listInstallations.mockResolvedValue({ headers: { etag: '123', }, @@ -121,7 +117,6 @@ describe('GithubCredentialsProvider tests', () => { }, ], } as RestEndpointMethodTypes['apps']['listInstallations']['response']); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ data: { @@ -133,17 +128,13 @@ describe('GithubCredentialsProvider tests', () => { const { token, headers } = await github.getCredentials({ url: 'https://github.com/backstage', }); - const { token: accessToken2 } = await github.getCredentials({ - url: 'https://github.com/backstage', - }); expect(headers).toEqual({ Authorization: 'Bearer secret_token' }); expect(token).toEqual('secret_token'); - expect(token).toEqual(accessToken2); }); it('should fail to issue tokens for an organization when the app is installed for a single repo', async () => { - octokit.apps.listInstallations.mockResolvedValueOnce({ + octokit.apps.listInstallations.mockResolvedValue({ headers: { etag: '123', }, @@ -157,7 +148,6 @@ describe('GithubCredentialsProvider tests', () => { }, ], } as RestEndpointMethodTypes['apps']['listInstallations']['response']); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ data: { @@ -176,7 +166,7 @@ describe('GithubCredentialsProvider tests', () => { }); it('should throw if the app is suspended', async () => { - octokit.apps.listInstallations.mockResolvedValueOnce({ + octokit.apps.listInstallations.mockResolvedValue({ headers: { etag: '123', }, @@ -193,7 +183,6 @@ describe('GithubCredentialsProvider tests', () => { }, ], } as RestEndpointMethodTypes['apps']['listInstallations']['response']); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); await expect( github.getCredentials({ @@ -229,7 +218,7 @@ describe('GithubCredentialsProvider tests', () => { ).resolves.toEqual(expect.objectContaining({ token: 'fallback_token' })); }); - it('should return the configured token if listing installations throws', async () => { + it('should return the configured token if there are no installations', async () => { const githubProvider = GithubCredentialsProvider.create({ host: 'github.com', apps: [ @@ -243,7 +232,9 @@ describe('GithubCredentialsProvider tests', () => { ], token: 'hardcoded_token', }); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); + octokit.apps.listInstallations.mockResolvedValue(({ + data: [], + } as unknown) as RestEndpointMethodTypes['apps']['listInstallations']['response']); await expect( githubProvider.getCredentials({ diff --git a/packages/integration/src/github/GithubCredentialsProvider.ts b/packages/integration/src/github/GithubCredentialsProvider.ts index 4bc6411870..899b233195 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.ts +++ b/packages/integration/src/github/GithubCredentialsProvider.ts @@ -66,7 +66,6 @@ const HEADERS = { class GithubAppManager { private readonly appClient: Octokit; private readonly baseAuthConfig: { appId: number; privateKey: string }; - private installations?: RestEndpointMethodTypes['apps']['listInstallations']['response']; private readonly cache = new Cache(); constructor(config: GithubAppConfig, baseUrl?: string) { @@ -121,22 +120,15 @@ class GithubAppManager { }); } + getInstallations(): Promise< + RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] + > { + return this.appClient.paginate(this.appClient.apps.listInstallations); + } + private async getInstallationData(owner: string): Promise { - // List all installations using the last used etag. - // Return cached InstallationData if error with status 304 is thrown. - try { - this.installations = await this.appClient.apps.listInstallations({ - headers: { - 'If-None-Match': this.installations?.headers.etag, - Accept: HEADERS.Accept, - }, - }); - } catch (error) { - if (error.status !== 304) { - throw error; - } - } - const installation = this.installations?.data.find( + const allInstallations = await this.getInstallations(); + const installation = allInstallations.find( inst => inst.account?.login === owner, ); if (installation) { @@ -163,6 +155,20 @@ export class GithubAppCredentialsMux { config.apps?.map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? []; } + async getAllInstallations(): Promise< + RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] + > { + if (!this.apps.length) { + return []; + } + + const installs = await Promise.all( + this.apps.map(app => app.getInstallations()), + ); + + return installs.flat(); + } + async getAppToken(owner: string, repo?: string): Promise { if (this.apps.length === 0) { return undefined; diff --git a/packages/integration/src/github/index.ts b/packages/integration/src/github/index.ts index d3caf19713..9247c4ff8e 100644 --- a/packages/integration/src/github/index.ts +++ b/packages/integration/src/github/index.ts @@ -20,6 +20,9 @@ export { } from './config'; export type { GitHubIntegrationConfig } from './config'; export { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; -export { GithubCredentialsProvider } from './GithubCredentialsProvider'; +export { + GithubAppCredentialsMux, + GithubCredentialsProvider, +} from './GithubCredentialsProvider'; export type { GithubCredentialType } from './GithubCredentialsProvider'; export { GitHubIntegration } from './GitHubIntegration'; diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 80d48f9dd7..c39060a934 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/techdocs-common +## 0.6.6 + +### Patch Changes + +- ab5cc376f: Use new utilities from `@backstage/backend-common` for safely resolving child paths +- b47fc34bc: Update "service catalog" references to "software catalog" +- Updated dependencies + - @backstage/backend-common@0.8.4 + - @backstage/integration@0.5.7 + ## 0.6.5 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 0a545ce805..59b18d8e27 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.6.5", + "version": "0.6.6", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -38,11 +38,11 @@ "dependencies": { "@azure/identity": "^1.2.2", "@azure/storage-blob": "^12.4.0", - "@backstage/backend-common": "^0.8.2", + "@backstage/backend-common": "^0.8.4", "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.6", + "@backstage/integration": "^0.5.7", "@google-cloud/storage": "^5.6.0", "@types/express": "^4.17.6", "aws-sdk": "^2.840.0", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.2", + "@backstage/cli": "^0.7.3", "@types/fs-extra": "^9.0.5", "@types/git-url-parse": "^9.0.0", "@types/js-yaml": "^4.0.0", diff --git a/packages/techdocs-common/src/stages/generate/helpers.ts b/packages/techdocs-common/src/stages/generate/helpers.ts index 718ee9f924..086377b4ef 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.ts @@ -15,6 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; +import { isChildPath } from '@backstage/backend-common'; import { spawn } from 'child_process'; import fs from 'fs-extra'; import yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml'; @@ -23,7 +24,6 @@ import { Logger } from 'winston'; import { ParsedLocationAnnotation } from '../../helpers'; import { RemoteProtocol } from '../prepare/types'; import { SupportedGeneratorKey } from './types'; -import { resolve as resolvePath } from 'path'; // TODO: Implement proper support for more generators. export function getGeneratorKey(entity: Entity): SupportedGeneratorKey { @@ -178,10 +178,7 @@ export const validateMkdocsYaml = async ( schema: MKDOCS_SCHEMA, }); - if ( - mkdocsYml.docs_dir && - !resolvePath(inputDir, mkdocsYml.docs_dir).startsWith(inputDir) - ) { + if (mkdocsYml.docs_dir && !isChildPath(inputDir, mkdocsYml.docs_dir)) { throw new Error( `docs_dir configuration value in mkdocs can't be an absolute directory or start with ../ for security reasons. Use relative paths instead which are resolved relative to your mkdocs.yml file location.`, diff --git a/packages/techdocs-common/src/stages/prepare/types.ts b/packages/techdocs-common/src/stages/prepare/types.ts index 8fe18984ca..d6f4e79062 100644 --- a/packages/techdocs-common/src/stages/prepare/types.ts +++ b/packages/techdocs-common/src/stages/prepare/types.ts @@ -29,10 +29,10 @@ export type PreparerResponse = { export type PreparerBase = { /** - * Given an Entity definition from the Service Catalog, go and prepare a directory + * Given an Entity definition from the Software Catalog, go and prepare a directory * with contents from the location in temporary storage and return the path. * - * @param entity The entity from the Service Catalog + * @param entity The entity from the Software Catalog * @param options.etag (Optional) If etag is provider, it will be used to check if the target has * updated since the last build. * @throws {NotModifiedError} when the prepared directory has not been changed since the last build. diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index b8fa785122..fbec69278e 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -31,7 +31,7 @@ "dependencies": { "@asyncapi/react-component": "^0.23.0", "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog": "^0.6.4", "@backstage/plugin-catalog-react": "^0.2.4", @@ -51,8 +51,8 @@ "swagger-ui-react": "^3.37.2" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 07662d46ff..822ec104e0 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend +## 0.3.15 + +### Patch Changes + +- 6ca29b66c: Unbreak `.well-known` OIDC routes +- 72574ac4d: Show better error message when configs defined under auth.providers. are undefined. +- Updated dependencies + - @backstage/backend-common@0.8.4 + - @backstage/catalog-client@0.3.15 + ## 0.3.14 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index d2b15a9d2d..98c9d8b62f 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.3.14", + "version": "0.3.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.3", - "@backstage/catalog-client": "^0.3.14", + "@backstage/backend-common": "^0.8.4", + "@backstage/catalog-client": "^0.3.15", "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", @@ -68,7 +68,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.2", + "@backstage/cli": "^0.7.3", "@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/service/router.ts b/plugins/auth-backend/src/service/router.ts index e988fde97c..8173efcbc1 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -144,11 +144,6 @@ export async function createRouter({ } } - router.use('/:provider/', req => { - const { provider } = req.params; - throw new NotFoundError(`Unknown auth provider '${provider}'`); - }); - router.use( createOidcRouter({ tokenIssuer, @@ -156,5 +151,10 @@ export async function createRouter({ }), ); + router.use('/:provider/', req => { + const { provider } = req.params; + throw new NotFoundError(`Unknown auth provider '${provider}'`); + }); + return router; } diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 79111df09e..cebc5332e2 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.2.4", @@ -35,8 +35,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 3d970a3e8b..450ddaca85 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -38,8 +38,8 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/catalog-backend-module-ldap/.eslintrc.js b/plugins/catalog-backend-module-ldap/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md new file mode 100644 index 0000000000..3806a6b5d3 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -0,0 +1,8 @@ +# @backstage/plugin-catalog-backend-module-ldap + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.11.0 diff --git a/plugins/catalog-backend-module-ldap/README.md b/plugins/catalog-backend-module-ldap/README.md new file mode 100644 index 0000000000..2bc34ba949 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/README.md @@ -0,0 +1,10 @@ +# Catalog Backend Module for LDAP + +This is an extension module to the plugin-catalog-backend plugin, providing an LdapOrgReaderProcessor that can be +used to ingest organization data from an LDAP server. This processor is useful if you want to import users and +groups from your Active Directory or another LDAP compatible server. + +## Getting started + +See [Backstage documentation](https://backstage.io/docs/integrations/ldap/org) for details on how to install +and configure the plugin. diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md new file mode 100644 index 0000000000..d5e9d731ea --- /dev/null +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -0,0 +1,72 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-ldap" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; +import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; +import { Client } from 'ldapjs'; +import { Config } from '@backstage/config'; +import { GroupEntity } from '@backstage/catalog-model'; +import { JsonValue } from '@backstage/config'; +import { LocationSpec } from '@backstage/catalog-model'; +import { Logger } from 'winston'; +import { SearchEntry } from 'ldapjs'; +import { SearchOptions } from 'ldapjs'; +import { UserEntity } from '@backstage/catalog-model'; + +// @public +export const LDAP_DN_ANNOTATION = "backstage.io/ldap-dn"; + +// @public +export const LDAP_RDN_ANNOTATION = "backstage.io/ldap-rdn"; + +// @public +export const LDAP_UUID_ANNOTATION = "backstage.io/ldap-uuid"; + +// @public +export class LdapClient { + constructor(client: Client); + // (undocumented) + static create(logger: Logger, target: string, bind?: BindConfig): Promise; + getRootDSE(): Promise; + getVendor(): Promise; + search(dn: string, options: SearchOptions): Promise; + } + +// @public +export class LdapOrgReaderProcessor implements CatalogProcessor { + constructor(options: { + providers: LdapProviderConfig[]; + logger: Logger; + }); + // (undocumented) + static fromConfig(config: Config, options: { + logger: Logger; + }): LdapOrgReaderProcessor; + // (undocumented) + readLocation(location: LocationSpec, _optional: boolean, emit: CatalogProcessorEmit): Promise; +} + +// @public +export type LdapProviderConfig = { + target: string; + bind?: BindConfig; + users: UserConfig; + groups: GroupConfig; +}; + +// @public +export function readLdapConfig(config: Config): LdapProviderConfig[]; + +// @public +export function readLdapOrg(client: LdapClient, userConfig: UserConfig, groupConfig: GroupConfig): Promise<{ + users: UserEntity[]; + groups: GroupEntity[]; +}>; + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts new file mode 100644 index 0000000000..57781fd711 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -0,0 +1,234 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { JsonValue } from '@backstage/config'; + +export interface Config { + /** + * Configuration options for the catalog plugin. + */ + catalog?: { + /** + * List of processor-specific options and attributes + */ + processors?: { + /** + * LdapOrgReaderProcessor configuration + */ + ldapOrg?: { + /** + * The configuration parameters for each single LDAP provider. + */ + providers: Array<{ + /** + * The prefix of the target that this matches on, e.g. + * "ldaps://ds.example.net", with no trailing slash. + */ + target: string; + + /** + * The settings to use for the bind command. If none are specified, + * the bind command is not issued. + */ + bind?: { + /** + * The DN of the user to auth as. + * + * E.g. "uid=ldap-robot,ou=robots,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The secret of the user to auth as (its password). + * + * @visibility secret + */ + secret: string; + }; + + /** + * The settings that govern the reading and interpretation of users. + */ + users: { + /** + * The DN under which users are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "uid". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "uid". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. Defaults to + * "mail". + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.memberOf field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + }; + }; + + /** + * The settings that govern the reading and interpretation of groups. + */ + groups: { + /** + * The DN under which groups are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "cn". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "cn". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. Defaults to + * "description". + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.type field of the entity. Defaults to "groupType". + */ + type?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.parent field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.children field of the entity. Defaults to "member". + */ + members?: string; + }; + }; + }>; + }; + }; + }; +} diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json new file mode 100644 index 0000000000..4d840b7f48 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/package.json @@ -0,0 +1,50 @@ +{ + "name": "@backstage/plugin-catalog-backend-module-ldap", + "version": "0.1.1", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-backend-module-ldap" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/catalog-model": "^0.8.3", + "@backstage/config": "^0.1.5", + "@backstage/plugin-catalog-backend": "^0.11.0", + "@types/ldapjs": "^1.0.10", + "ldapjs": "^2.2.0", + "lodash": "^4.17.15", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.7.3", + "@backstage/test-utils": "^0.1.13", + "@types/lodash": "^4.14.151", + "msw": "^0.29.0" + }, + "files": [ + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/catalog-backend-module-ldap/src/index.ts b/plugins/catalog-backend-module-ldap/src/index.ts new file mode 100644 index 0000000000..25353df4c4 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/src/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './processors'; +export * from './ldap'; diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/ldap/client.ts rename to plugins/catalog-backend-module-ldap/src/ldap/client.ts diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/config.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/ldap/config.test.ts rename to plugins/catalog-backend-module-ldap/src/ldap/config.test.ts diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts similarity index 99% rename from plugins/catalog-backend/src/ingestion/processors/ldap/config.ts rename to plugins/catalog-backend-module-ldap/src/ldap/config.ts index 70aa729e84..eefe43f97a 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -17,7 +17,7 @@ import { Config, JsonValue } from '@backstage/config'; import { SearchOptions } from 'ldapjs'; import mergeWith from 'lodash/mergeWith'; -import { RecursivePartial } from '../../../util'; +import { RecursivePartial } from '@backstage/plugin-catalog-backend'; /** * The configuration parameters for a single LDAP provider. diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/constants.ts b/plugins/catalog-backend-module-ldap/src/ldap/constants.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/ldap/constants.ts rename to plugins/catalog-backend-module-ldap/src/ldap/constants.ts diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/index.ts b/plugins/catalog-backend-module-ldap/src/ldap/index.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/ldap/index.ts rename to plugins/catalog-backend-module-ldap/src/ldap/index.ts diff --git a/plugins/catalog-backend-module-ldap/src/ldap/org.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/org.test.ts new file mode 100644 index 0000000000..254f511c4f --- /dev/null +++ b/plugins/catalog-backend-module-ldap/src/ldap/org.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { buildMemberOf, buildOrgHierarchy } from './org'; + +function g( + name: string, + parent: string | undefined, + children: string[], +): GroupEntity { + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { name }, + spec: { type: 'team', parent, children }, + }; +} + +describe('buildOrgHierarchy', () => { + it('adds groups to their parent.children', () => { + const a = g('a', undefined, []); + const b = g('b', 'a', []); + const c = g('c', 'b', []); + const d = g('d', 'a', []); + buildOrgHierarchy([a, b, c, d]); + expect(a.spec.children).toEqual(expect.arrayContaining(['b', 'd'])); + expect(b.spec.children).toEqual(expect.arrayContaining(['c'])); + expect(c.spec.children).toEqual([]); + expect(d.spec.children).toEqual([]); + }); + + it('sets parent of groups children', () => { + const a = g('a', undefined, ['b', 'd']); + const b = g('b', undefined, ['c']); + const c = g('c', undefined, []); + const d = g('d', undefined, []); + buildOrgHierarchy([a, b, c, d]); + expect(a.spec.parent).toBeUndefined(); + expect(b.spec.parent).toBe('a'); + expect(c.spec.parent).toBe('b'); + expect(d.spec.parent).toBe('a'); + }); +}); + +describe('buildMemberOf', () => { + it('fills indirect member of groups', () => { + const a = g('a', undefined, []); + const b = g('b', 'a', []); + const c = g('c', 'b', []); + const u: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { name: 'n' }, + spec: { profile: {}, memberOf: ['c'] }, + }; + + const groups = [a, b, c]; + buildOrgHierarchy(groups); + buildMemberOf(groups, [u]); + expect(u.spec.memberOf).toEqual(expect.arrayContaining(['a', 'b', 'c'])); + }); + + it('takes group spec.members into account', () => { + const a = g('a', undefined, []); + const b = g('b', 'a', []); + const c = g('c', 'b', []); + c.spec.members = ['n']; + const u: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { name: 'n' }, + spec: { profile: {}, memberOf: [] }, + }; + + const groups = [a, b, c]; + buildOrgHierarchy(groups); + buildMemberOf(groups, [u]); + expect(u.spec.memberOf).toEqual(expect.arrayContaining(['a', 'b', 'c'])); + }); +}); diff --git a/plugins/catalog-backend-module-ldap/src/ldap/org.ts b/plugins/catalog-backend-module-ldap/src/ldap/org.ts new file mode 100644 index 0000000000..fe59451a9d --- /dev/null +++ b/plugins/catalog-backend-module-ldap/src/ldap/org.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GroupEntity, UserEntity } from '@backstage/catalog-model'; + +// TODO: Copied from plugin-catalog-backend, but we could also export them from +// there. Or move them to catalog-model. + +export function buildOrgHierarchy(groups: GroupEntity[]) { + const groupsByName = new Map(groups.map(g => [g.metadata.name, g])); + + // + // Make sure that g.parent.children contain g + // + + for (const group of groups) { + const selfName = group.metadata.name; + const parentName = group.spec.parent; + if (parentName) { + const parent = groupsByName.get(parentName); + if (parent && !parent.spec.children.includes(selfName)) { + parent.spec.children.push(selfName); + } + } + } + + // + // Make sure that g.children.parent is g + // + + for (const group of groups) { + const selfName = group.metadata.name; + for (const childName of group.spec.children) { + const child = groupsByName.get(childName); + if (child && !child.spec.parent) { + child.spec.parent = selfName; + } + } + } +} + +// Ensure that users have their transitive group memberships. Requires that +// the groups were previously processed with buildOrgHierarchy() +export function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) { + const groupsByName = new Map(groups.map(g => [g.metadata.name, g])); + + users.forEach(user => { + const transitiveMemberOf = new Set(); + + const todo = [ + ...user.spec.memberOf, + ...groups + .filter(g => g.spec.members?.includes(user.metadata.name)) + .map(g => g.metadata.name), + ]; + + for (;;) { + const current = todo.pop(); + if (!current) { + break; + } + + if (!transitiveMemberOf.has(current)) { + transitiveMemberOf.add(current); + const group = groupsByName.get(current); + if (group?.spec.parent) { + todo.push(group.spec.parent); + } + } + } + + user.spec.memberOf = [...transitiveMemberOf]; + }); +} diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts similarity index 99% rename from plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts rename to plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index e043a644ce..b357b6b408 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -17,7 +17,7 @@ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { SearchEntry } from 'ldapjs'; import merge from 'lodash/merge'; -import { RecursivePartial } from '../../../util'; +import { RecursivePartial } from '@backstage/plugin-catalog-backend'; import { LdapClient } from './client'; import { GroupConfig, UserConfig } from './config'; import { diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts similarity index 99% rename from plugins/catalog-backend/src/ingestion/processors/ldap/read.ts rename to plugins/catalog-backend-module-ldap/src/ldap/read.ts index fb740e5e0a..a662b36d87 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -17,7 +17,7 @@ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { SearchEntry } from 'ldapjs'; import lodashSet from 'lodash/set'; -import { buildOrgHierarchy } from '../util/org'; +import { buildOrgHierarchy } from './org'; import { LdapClient } from './client'; import { GroupConfig, UserConfig } from './config'; import { diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/util.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/util.test.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/ldap/util.test.ts rename to plugins/catalog-backend-module-ldap/src/ldap/util.test.ts diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/util.ts b/plugins/catalog-backend-module-ldap/src/ldap/util.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/ldap/util.ts rename to plugins/catalog-backend-module-ldap/src/ldap/util.ts diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/vendors.ts b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/ldap/vendors.ts rename to plugins/catalog-backend-module-ldap/src/ldap/vendors.ts diff --git a/plugins/catalog-backend/src/ingestion/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts similarity index 95% rename from plugins/catalog-backend/src/ingestion/processors/LdapOrgReaderProcessor.ts rename to plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts index ab13e1f19f..79c0ed6964 100644 --- a/plugins/catalog-backend/src/ingestion/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts @@ -22,9 +22,12 @@ import { LdapProviderConfig, readLdapConfig, readLdapOrg, -} from './ldap'; -import * as results from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; +} from '../ldap'; +import { + CatalogProcessor, + CatalogProcessorEmit, + results, +} from '@backstage/plugin-catalog-backend'; /** * Extracts teams and users out of an LDAP server. diff --git a/plugins/catalog-backend-module-ldap/src/processors/index.ts b/plugins/catalog-backend-module-ldap/src/processors/index.ts new file mode 100644 index 0000000000..6e3f413085 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/src/processors/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { LdapOrgReaderProcessor } from './LdapOrgReaderProcessor'; diff --git a/plugins/catalog-backend-module-ldap/src/setupTests.ts b/plugins/catalog-backend-module-ldap/src/setupTests.ts new file mode 100644 index 0000000000..d3232290a7 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index e85601ab7c..6de8b0ec07 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.2.0 + +### Minor Changes + +- 115473c08: Handle error gracefully if failure occurs while loading photos using Microsoft Graph API. + + This includes a breaking change: you now have to pass the `options` object to `readMicrosoftGraphUsers` and `readMicrosoftGraphOrg`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.11.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index 702601b469..f1e7e22c35 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -103,10 +103,11 @@ export type OrganizationTransformer = (organization: MicrosoftGraph.Organization export function readMicrosoftGraphConfig(config: Config): MicrosoftGraphProviderConfig[]; // @public (undocumented) -export function readMicrosoftGraphOrg(client: MicrosoftGraphClient, tenantId: string, options?: { +export function readMicrosoftGraphOrg(client: MicrosoftGraphClient, tenantId: string, options: { userFilter?: string; groupFilter?: string; groupTransformer?: GroupTransformer; + logger: Logger; }): Promise<{ users: UserEntity[]; groups: GroupEntity[]; diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index f37b954706..1c1886d802 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.1.1", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "@azure/msal-node": "^1.1.0", "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", - "@backstage/plugin-catalog-backend": "^0.10.4", + "@backstage/plugin-catalog-backend": "^0.11.0", "@microsoft/microsoft-graph-types": "^1.25.0", "cross-fetch": "^3.0.6", "lodash": "^4.17.15", @@ -40,7 +40,8 @@ "qs": "^6.9.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", + "@backstage/backend-common": "^0.8.4", + "@backstage/cli": "^0.7.3", "@backstage/test-utils": "^0.1.14", "@types/lodash": "^4.14.151", "msw": "^0.29.0" diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts index b1179c20df..929d01e128 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -23,6 +23,7 @@ import { readMicrosoftGraphUsers, resolveRelations, } from './read'; +import { getVoidLogger } from '@backstage/backend-common'; function user(data: Partial): UserEntity { return merge( @@ -84,6 +85,7 @@ describe('read microsoft graph', () => { const { users } = await readMicrosoftGraphUsers(client, { userFilter: 'accountEnabled eq true', + logger: getVoidLogger(), }); expect(users).toEqual([ diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 70c403d0b7..a9e2465ad7 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -33,6 +33,7 @@ import { OrganizationTransformer, UserTransformer, } from './types'; +import { Logger } from 'winston'; export async function defaultUserTransformer( user: MicrosoftGraph.User, @@ -75,7 +76,11 @@ export async function defaultUserTransformer( export async function readMicrosoftGraphUsers( client: MicrosoftGraphClient, - options?: { userFilter?: string; transformer?: UserTransformer }, + options: { + userFilter?: string; + transformer?: UserTransformer; + logger: Logger; + }, ): Promise<{ users: UserEntity[]; // With all relations empty }> { @@ -86,17 +91,22 @@ export async function readMicrosoftGraphUsers( const promises: Promise[] = []; for await (const user of client.getUsers({ - filter: options?.userFilter, + filter: options.userFilter, })) { // Process all users in parallel, otherwise it can take quite some time promises.push( limiter(async () => { - const userPhoto = await client.getUserPhotoWithSizeLimit( - user.id!, - // We are limiting the photo size, as users with full resolution photos - // can make the Backstage API slow - 120, - ); + let userPhoto; + try { + userPhoto = await client.getUserPhotoWithSizeLimit( + user.id!, + // We are limiting the photo size, as users with full resolution photos + // can make the Backstage API slow + 120, + ); + } catch (e) { + options.logger.warn(`Unable to load photo for ${user.id}`); + } const entity = await transformer(user, userPhoto); @@ -371,14 +381,16 @@ export function resolveRelations( export async function readMicrosoftGraphOrg( client: MicrosoftGraphClient, tenantId: string, - options?: { + options: { userFilter?: string; groupFilter?: string; groupTransformer?: GroupTransformer; + logger: Logger; }, ): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> { const { users } = await readMicrosoftGraphUsers(client, { - userFilter: options?.userFilter, + userFilter: options.userFilter, + logger: options.logger, }); const { groups, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts index d86584a9fd..88240db618 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -90,6 +90,7 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { userFilter: provider.userFilter, groupFilter: provider.groupFilter, groupTransformer: this.groupTransformer, + logger: this.logger, }, ); diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 0f2ca0a54d..174f279b11 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,95 @@ # @backstage/plugin-catalog-backend +## 0.11.0 + +### Minor Changes + +- 45af985df: Handle entity name conflicts in a deterministic way and avoid crashes due to naming conflicts at startup. + + This is a breaking change for the database and entity provider interfaces of the new catalog. The interfaces with breaking changes are `EntityProvider` and `ProcessingDatabase`, and while it's unlikely that these interfaces have much usage yet, a migration guide is provided below. + + The breaking change to the `EntityProvider` interface lies within the items passed in the `EntityProviderMutation` type. Rather than passing along entities directly, they are now wrapped up in a `DeferredEntity` type, which is a tuple of an `entity` and a `locationKey`. The `entity` houses the entity as it was passed on before, while the `locationKey` is a new concept that is used for conflict resolution within the catalog. + + The `locationKey` is an opaque string that should be unique for each location that an entity could be located at, and undefined if the entity does not have a fixed location. In practice it should be set to the serialized location reference if the entity is stored in Git, for example `https://github.com/backstage/backstage/blob/master/catalog-info.yaml`. A conflict between two entity definitions happen when they have the same entity reference, i.e. kind, namespace, and name. In the event of a conflict the location key will be used according to the following rules to resolve the conflict: + + - If the entity is already present in the database but does not have a location key set, the new entity wins and will override the existing one. + - If the entity is already present in the database the new entity will only win if the location keys of the existing and new entity are the same. + - If the entity is not already present, insert the entity into the database along with the provided location key. + + The breaking change to the `ProcessingDatabase` is similar to the one for the entity provider, as it reflects the switch from `Entity` to `DeferredEntity` in the `ReplaceUnprocessedEntitiesOptions`. In addition, the `addUnprocessedEntities` method has been removed from the `ProcessingDatabase` interface, and the `RefreshStateItem` and `UpdateProcessedEntityOptions` types have received a new optional `locationKey` property. + +- 8e533f92c: Move `LdapOrgReaderProcessor` from `@backstage/plugin-catalog-backend` + to `@backstage/plugin-catalog-backend-module-ldap`. + + The `LdapOrgReaderProcessor` isn't registered by default anymore, if + you want to continue using it you have to register it manually at the catalog + builder: + + 1. Add dependency to `@backstage/plugin-catalog-backend-module-ldap` to the `package.json` of your backend. + 2. Add the processor to the catalog builder: + + ```typescript + // packages/backend/src/plugins/catalog.ts + builder.addProcessor( + LdapOrgReaderProcessor.fromConfig(config, { + logger, + }), + ); + ``` + + For more configuration details, see the [README of the `@backstage/plugin-catalog-backend-module-ldap` package](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md). + +### Patch Changes + +- 22a60518c: Support ingesting multiple GitHub organizations via a new `GithubMultiOrgReaderProcessor`. + + This new processor handles namespacing created groups according to the org of the associated GitHub team to prevent potential name clashes between organizations. Be aware that this processor is considered alpha and may not be compatible with future org structures in the catalog. + + NOTE: This processor only fully supports auth via GitHub Apps + + To install this processor, import and add it as follows: + + ```typescript + // Typically in packages/backend/src/plugins/catalog.ts + import { GithubMultiOrgReaderProcessor } from '@backstage/plugin-catalog-backend'; + // ... + export default async function createPlugin(env: PluginEnvironment) { + const builder = new CatalogBuilder(env); + builder.addProcessor( + GithubMultiOrgReaderProcessor.fromConfig(env.config, { + logger: env.logger, + }), + ); + // ... + } + ``` + + Configure in your `app-config.yaml` by pointing to your GitHub instance and optionally list which GitHub organizations you wish to import. You can also configure what namespace you want to set for teams from each org. If unspecified, the org name will be used as the namespace. If no organizations are listed, by default this processor will import from all organizations accessible by all configured GitHub Apps: + + ```yaml + catalog: + locations: + - type: github-multi-org + target: https://github.myorg.com + + processors: + githubMultiOrg: + orgs: + - name: fooOrg + groupNamespace: foo + - name: barOrg + groupNamespace: bar + - name: awesomeOrg + - name: anotherOrg + ``` + +- d408af872: Only return the selected fields from the new catalog. +- aa2b15d9d: Ensure that emitted relations are deduplicated +- Updated dependencies + - @backstage/backend-common@0.8.4 + - @backstage/integration@0.5.7 + - @backstage/catalog-client@0.3.15 + ## 0.10.4 ### Patch Changes diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 04f76e06c7..7da9cad139 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -146,207 +146,23 @@ export interface Config { }; /** - * LdapOrgReaderProcessor configuration + * GithubMultiOrgReaderProcessor configuration */ - ldapOrg?: { + githubMultiOrg?: { /** - * The configuration parameters for each single LDAP provider. + * The configuration parameters for each GitHub org to process. */ - providers: Array<{ + orgs: Array<{ /** - * The prefix of the target that this matches on, e.g. - * "ldaps://ds.example.net", with no trailing slash. + * The name of the GitHub org to process. */ - target: string; - + name: string; /** - * The settings to use for the bind command. If none are specified, - * the bind command is not issued. + * The namespace of the group created for this org. + * + * Defaults to org name if omitted. */ - bind?: { - /** - * The DN of the user to auth as. - * - * E.g. "uid=ldap-robot,ou=robots,ou=example,dc=example,dc=net" - */ - dn: string; - /** - * The secret of the user to auth as (its password). - * - * @visibility secret - */ - secret: string; - }; - - /** - * The settings that govern the reading and interpretation of users. - */ - users: { - /** - * The DN under which users are stored. - * - * E.g. "ou=people,ou=example,dc=example,dc=net" - */ - dn: string; - /** - * The search options to use. The default is scope "one" and - * attributes "*" and "+". - * - * It is common to want to specify a filter, to narrow down the set - * of matching items. - */ - options: { - scope?: 'base' | 'one' | 'sub'; - filter?: string; - attributes?: string | string[]; - paged?: - | boolean - | { - pageSize?: number; - pagePause?: boolean; - }; - }; - /** - * JSON paths (on a.b.c form) and hard coded values to set on those - * paths. - * - * This can be useful for example if you want to hard code a - * namespace or similar on the generated entities. - */ - set?: { [key: string]: JsonValue }; - /** - * Mappings from well known entity fields, to LDAP attribute names - */ - map?: { - /** - * The name of the attribute that holds the relative - * distinguished name of each entry. Defaults to "uid". - */ - rdn?: string; - /** - * The name of the attribute that shall be used for the value of - * the metadata.name field of the entity. Defaults to "uid". - */ - name?: string; - /** - * The name of the attribute that shall be used for the value of - * the metadata.description field of the entity. - */ - description?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.displayName field of the entity. Defaults to - * "cn". - */ - displayName?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.email field of the entity. Defaults to - * "mail". - */ - email?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.picture field of the entity. - */ - picture?: string; - /** - * The name of the attribute that shall be used for the values of - * the spec.memberOf field of the entity. Defaults to "memberOf". - */ - memberOf?: string; - }; - }; - - /** - * The settings that govern the reading and interpretation of groups. - */ - groups: { - /** - * The DN under which groups are stored. - * - * E.g. "ou=people,ou=example,dc=example,dc=net" - */ - dn: string; - /** - * The search options to use. The default is scope "one" and - * attributes "*" and "+". - * - * It is common to want to specify a filter, to narrow down the set - * of matching items. - */ - options: { - scope?: 'base' | 'one' | 'sub'; - filter?: string; - attributes?: string | string[]; - paged?: - | boolean - | { - pageSize?: number; - pagePause?: boolean; - }; - }; - /** - * JSON paths (on a.b.c form) and hard coded values to set on those - * paths. - * - * This can be useful for example if you want to hard code a - * namespace or similar on the generated entities. - */ - set?: { [key: string]: JsonValue }; - /** - * Mappings from well known entity fields, to LDAP attribute names - */ - map?: { - /** - * The name of the attribute that holds the relative - * distinguished name of each entry. Defaults to "cn". - */ - rdn?: string; - /** - * The name of the attribute that shall be used for the value of - * the metadata.name field of the entity. Defaults to "cn". - */ - name?: string; - /** - * The name of the attribute that shall be used for the value of - * the metadata.description field of the entity. Defaults to - * "description". - */ - description?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.type field of the entity. Defaults to "groupType". - */ - type?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.displayName field of the entity. Defaults to - * "cn". - */ - displayName?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.email field of the entity. - */ - email?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.picture field of the entity. - */ - picture?: string; - /** - * The name of the attribute that shall be used for the values of - * the spec.parent field of the entity. Defaults to "memberOf". - */ - memberOf?: string; - /** - * The name of the attribute that shall be used for the values of - * the spec.children field of the entity. Defaults to "member". - */ - members?: string; - }; - }; + groupNamespace?: string; }>; }; diff --git a/plugins/catalog-backend/migrations/20210622104022_refresh_state_location_key.js b/plugins/catalog-backend/migrations/20210622104022_refresh_state_location_key.js new file mode 100644 index 0000000000..37ab5e12cb --- /dev/null +++ b/plugins/catalog-backend/migrations/20210622104022_refresh_state_location_key.js @@ -0,0 +1,40 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('refresh_state', table => { + table + .text('location_key') + .nullable() + .comment( + 'An opaque key that uniquely identifies the location of an entity in order to support conflict resolution', + ); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.alterTable('refresh_state', table => { + table.dropColumn('location_key'); + }); +}; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 1ad33b1589..3a0464bff6 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.10.4", + "version": "0.11.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,17 +29,16 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.3", - "@backstage/catalog-client": "^0.3.14", + "@backstage/backend-common": "^0.8.4", + "@backstage/catalog-client": "^0.3.15", "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.6", + "@backstage/integration": "^0.5.7", "@backstage/plugin-search-backend-node": "^0.2.2", "@backstage/search-common": "^0.1.2", "@octokit/graphql": "^4.5.8", "@types/express": "^4.17.6", - "@types/ldapjs": "^1.0.9", "aws-sdk": "^2.840.0", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", @@ -51,7 +50,6 @@ "git-url-parse": "^11.4.4", "glob": "^7.1.6", "knex": "^0.95.1", - "ldapjs": "^2.2.0", "lodash": "^4.17.15", "morgan": "^1.10.0", "p-limit": "^3.0.2", @@ -64,7 +62,7 @@ }, "devDependencies": { "@backstage/backend-test-utils": "^0.1.3", - "@backstage/cli": "^0.7.2", + "@backstage/cli": "^0.7.3", "@backstage/test-utils": "^0.1.14", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts new file mode 100644 index 0000000000..5223acba4a --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts @@ -0,0 +1,181 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LocationSpec } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { + GithubAppCredentialsMux, + GithubCredentialsProvider, + GitHubIntegrationConfig, + ScmIntegrations, +} from '@backstage/integration'; +import { graphql } from '@octokit/graphql'; +import { Logger } from 'winston'; +import { + getOrganizationTeams, + getOrganizationUsers, + GithubMultiOrgConfig, + readGithubMultiOrgConfig, +} from './github'; +import * as results from './results'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { buildOrgHierarchy } from './util/org'; + +/** + * @alpha + * Extracts teams and users out of a multiple GitHub orgs namespaced per org. + * + * Be aware that this processor may not be compatible with future org structures in the catalog. + */ +export class GithubMultiOrgReaderProcessor implements CatalogProcessor { + private readonly integrations: ScmIntegrations; + private readonly orgs: GithubMultiOrgConfig; + private readonly logger: Logger; + + static fromConfig(config: Config, options: { logger: Logger }) { + const c = config.getOptionalConfig('catalog.processors.githubMultiOrg'); + const integrations = ScmIntegrations.fromConfig(config); + + return new GithubMultiOrgReaderProcessor({ + ...options, + integrations, + orgs: c ? readGithubMultiOrgConfig(c) : [], + }); + } + + constructor(options: { + integrations: ScmIntegrations; + logger: Logger; + orgs: GithubMultiOrgConfig; + }) { + this.integrations = options.integrations; + this.logger = options.logger; + this.orgs = options.orgs; + } + + async readLocation( + location: LocationSpec, + _optional: boolean, + emit: CatalogProcessorEmit, + ): Promise { + if (location.type !== 'github-multi-org') { + return false; + } + + const gitHubConfig = this.integrations.github.byUrl(location.target) + ?.config; + if (!gitHubConfig) { + throw new Error( + `There is no GitHub integration that matches ${location.target}. Please add a configuration entry for it under integrations.github`, + ); + } + + const allUsersMap = new Map(); + const baseUrl = new URL(location.target).origin; + const credentialsProvider = GithubCredentialsProvider.create(gitHubConfig); + + const orgsToProcess = this.orgs.length + ? this.orgs + : await this.getAllOrgs(gitHubConfig); + + for (const orgConfig of orgsToProcess) { + try { + const { + headers, + type: tokenType, + } = await credentialsProvider.getCredentials({ + url: `${baseUrl}/${orgConfig.name}`, + }); + const client = graphql.defaults({ + baseUrl: gitHubConfig.apiBaseUrl, + headers, + }); + + const startTimestamp = Date.now(); + this.logger.info( + `Reading GitHub users and teams for org: ${orgConfig.name}`, + ); + const { users } = await getOrganizationUsers( + client, + orgConfig.name, + tokenType, + ); + const { groups, groupMemberUsers } = await getOrganizationTeams( + client, + orgConfig.name, + orgConfig.groupNamespace, + ); + + const duration = ((Date.now() - startTimestamp) / 1000).toFixed(1); + this.logger.debug( + `Read ${users.length} GitHub users and ${groups.length} GitHub teams from ${orgConfig.name} in ${duration} seconds`, + ); + + users.forEach(u => { + if (!allUsersMap.has(u.metadata.name)) { + allUsersMap.set(u.metadata.name, u); + } + }); + + for (const [groupName, userNames] of groupMemberUsers.entries()) { + for (const userName of userNames) { + const user = allUsersMap.get(userName); + if (user && !user.spec.memberOf.includes(groupName)) { + user.spec.memberOf.push(groupName); + } + } + } + buildOrgHierarchy(groups); + + for (const group of groups) { + emit(results.entity(location, group)); + } + } catch (e) { + this.logger.error( + `Failed to read GitHub org data for ${orgConfig.name}: ${e}`, + ); + } + } + + const allUsers = Array.from(allUsersMap.values()); + for (const user of allUsers) { + emit(results.entity(location, user)); + } + + return true; + } + + // Note: Does not support usage of PATs + private async getAllOrgs( + gitHubConfig: GitHubIntegrationConfig, + ): Promise { + const githubAppMux = new GithubAppCredentialsMux(gitHubConfig); + const installs = await githubAppMux.getAllInstallations(); + + return installs + .map(install => + install.target_type === 'Organization' && + install.account && + install.account.login + ? { + name: install.account.login, + groupNamespace: install.account.login.toLowerCase(), + } + : undefined, + ) + .filter(Boolean) as GithubMultiOrgConfig; + } +} diff --git a/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts b/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts index 2eb033a8fe..7bdc28e6dd 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts @@ -15,7 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; -import { readGithubConfig } from './config'; +import { readGithubConfig, readGithubMultiOrgConfig } from './config'; describe('config', () => { describe('readGithubConfig', () => { @@ -76,4 +76,35 @@ describe('config', () => { ).toThrow(/token/); }); }); + + describe('readGithubMultiOrgConfig', () => { + function config(orgs: { name: string; groupNamespace?: string }[]) { + return new ConfigReader({ orgs }); + } + + it('reads org configs', () => { + const output = readGithubMultiOrgConfig( + config([ + { name: 'foo', groupNamespace: 'apple' }, + { name: 'bar', groupNamespace: 'Orange' }, + ]), + ); + + expect(output).toEqual([ + { name: 'foo', groupNamespace: 'apple' }, + { name: 'bar', groupNamespace: 'orange' }, + ]); + }); + + it('defaults groupNamespace to org name if undefined', () => { + const output = readGithubMultiOrgConfig( + config([{ name: 'foo' }, { name: 'bar' }]), + ); + + expect(output).toEqual([ + { name: 'foo', groupNamespace: 'foo' }, + { name: 'bar', groupNamespace: 'bar' }, + ]); + }); + }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/github/config.ts b/plugins/catalog-backend/src/ingestion/processors/github/config.ts index bd71ab8189..40305ff1f2 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/config.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/config.ts @@ -82,3 +82,27 @@ export function readGithubConfig(config: Config): ProviderConfig[] { return providers; } + +/** + * The configuration parameters for a multi-org GitHub processor. + */ +export type GithubMultiOrgConfig = Array<{ + /** + * The name of the GitHub org to process. + */ + name: string; + /** + * The namespace of the group created for this org. + */ + groupNamespace: string; +}>; + +export function readGithubMultiOrgConfig(config: Config): GithubMultiOrgConfig { + const orgConfigs = config.getOptionalConfigArray('orgs') ?? []; + return orgConfigs.map(c => ({ + name: c.getString('name'), + groupNamespace: ( + c.getOptionalString('groupNamespace') ?? c.getString('name') + ).toLowerCase(), + })); +} diff --git a/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts b/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts index 5ef5ae5faf..11cdf671c3 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts @@ -72,8 +72,10 @@ describe('github', () => { }); describe('getOrganizationTeams', () => { - it('reads teams', async () => { - const input: QueryResponse = { + let input: QueryResponse; + + beforeEach(() => { + input = { organization: { teams: { pageInfo: { hasNextPage: false }, @@ -98,7 +100,9 @@ describe('github', () => { }, }, }; + }); + it('reads teams', async () => { const output = { groups: [ expect.objectContaining({ @@ -126,6 +130,38 @@ describe('github', () => { await expect(getOrganizationTeams(graphql, 'a')).resolves.toEqual(output); }); + + it('applies namespaces', async () => { + const output = { + groups: [ + expect.objectContaining({ + metadata: expect.objectContaining({ + name: 'team', + namespace: 'foo', + description: 'The one and only team', + }), + spec: { + type: 'team', + profile: { + displayName: 'Team', + picture: 'http://example.com/team.jpeg', + }, + parent: 'parent', + children: [], + }, + }), + ], + groupMemberUsers: new Map([['foo/team', ['user']]]), + }; + + server.use( + graphqlMsw.query('teams', (_req, res, ctx) => res(ctx.data(input))), + ); + + await expect(getOrganizationTeams(graphql, 'a', 'foo')).resolves.toEqual( + output, + ); + }); }); describe('getTeamMembers', () => { diff --git a/plugins/catalog-backend/src/ingestion/processors/github/github.ts b/plugins/catalog-backend/src/ingestion/processors/github/github.ts index ab17698e98..7e1ef136eb 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/github.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/github.ts @@ -144,6 +144,7 @@ export async function getOrganizationUsers( export async function getOrganizationTeams( client: typeof graphql, org: string, + orgNamespace?: string, ): Promise<{ groups: GroupEntity[]; groupMemberUsers: Map; @@ -189,6 +190,10 @@ export async function getOrganizationTeams( }, }; + if (orgNamespace) { + entity.metadata.namespace = orgNamespace; + } + if (team.description) { entity.metadata.description = team.description; } @@ -203,7 +208,8 @@ export async function getOrganizationTeams( } const memberNames: string[] = []; - groupMemberUsers.set(team.slug, memberNames); + const groupKey = orgNamespace ? `${orgNamespace}/${team.slug}` : team.slug; + groupMemberUsers.set(groupKey, memberNames); if (!team.members.pageInfo.hasNextPage) { // We got all the members in one go, run the fast path diff --git a/plugins/catalog-backend/src/ingestion/processors/github/index.ts b/plugins/catalog-backend/src/ingestion/processors/github/index.ts index 2d75813b6d..7c14e21108 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/index.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -export { readGithubConfig } from './config'; -export type { ProviderConfig } from './config'; +export { readGithubConfig, readGithubMultiOrgConfig } from './config'; +export type { GithubMultiOrgConfig, ProviderConfig } from './config'; export { getOrganizationTeams, getOrganizationUsers, diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts index 5abe117fd3..6a44ea0a8f 100644 --- a/plugins/catalog-backend/src/ingestion/processors/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/index.ts @@ -25,7 +25,7 @@ export { CodeOwnersProcessor } from './CodeOwnersProcessor'; export { FileReaderProcessor } from './FileReaderProcessor'; export { GithubDiscoveryProcessor } from './GithubDiscoveryProcessor'; export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor'; -export { LdapOrgReaderProcessor } from './LdapOrgReaderProcessor'; +export { GithubMultiOrgReaderProcessor } from './GithubMultiOrgReaderProcessor'; export { LocationEntityProcessor } from './LocationEntityProcessor'; export { PlaceholderProcessor } from './PlaceholderProcessor'; export type { PlaceholderResolver } from './PlaceholderProcessor'; diff --git a/plugins/catalog-backend/src/next/ConfigLocationEntityProvider.test.ts b/plugins/catalog-backend/src/next/ConfigLocationEntityProvider.test.ts index 37d549c7f2..d177b1aa32 100644 --- a/plugins/catalog-backend/src/next/ConfigLocationEntityProvider.test.ts +++ b/plugins/catalog-backend/src/next/ConfigLocationEntityProvider.test.ts @@ -41,26 +41,32 @@ describe('ConfigLocationEntityProvider', () => { expect(mockConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', entities: expect.arrayContaining([ - expect.objectContaining({ - spec: { - target: path.join( - resolvePackagePath('@backstage/plugin-catalog-backend'), - './lols.yaml', - ), - type: 'file', - }, - }), + { + entity: expect.objectContaining({ + spec: { + target: path.join( + resolvePackagePath('@backstage/plugin-catalog-backend'), + './lols.yaml', + ), + type: 'file', + }, + }), + locationKey: expect.stringMatching(/lols\.yaml$/), + }, ]), }); expect(mockConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', entities: expect.arrayContaining([ - expect.objectContaining({ - spec: { - target: 'https://github.com/backstage/backstage', - type: 'url', - }, - }), + { + entity: expect.objectContaining({ + spec: { + target: 'https://github.com/backstage/backstage', + type: 'url', + }, + }), + locationKey: 'url:https://github.com/backstage/backstage', + }, ]), }); }); diff --git a/plugins/catalog-backend/src/next/ConfigLocationEntityProvider.ts b/plugins/catalog-backend/src/next/ConfigLocationEntityProvider.ts index 9218a9c0e1..71ec84dae7 100644 --- a/plugins/catalog-backend/src/next/ConfigLocationEntityProvider.ts +++ b/plugins/catalog-backend/src/next/ConfigLocationEntityProvider.ts @@ -16,6 +16,7 @@ import { Config } from '@backstage/config'; import path from 'path'; +import { getEntityLocationRef } from './processing/util'; import { EntityProvider, EntityProviderConnection } from './types'; import { locationSpecToLocationEntity } from './util'; @@ -37,10 +38,12 @@ export class ConfigLocationEntityProvider implements EntityProvider { const entities = locationConfigs.map(location => { const type = location.getString('type'); const target = location.getString('target'); - return locationSpecToLocationEntity({ + const entity = locationSpecToLocationEntity({ type, target: type === 'file' ? path.resolve(target) : target, }); + const locationKey = getEntityLocationRef(entity); + return { entity, locationKey }; }); await this.connection.applyMutation({ diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts index a504de0bf3..0ae20c94b1 100644 --- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts @@ -46,7 +46,7 @@ class Connection implements EntityProviderConnection { const db = this.config.processingDatabase; if (mutation.type === 'full') { - this.check(mutation.entities); + this.check(mutation.entities.map(e => e.entity)); await db.transaction(async tx => { await db.replaceUnprocessedEntities(tx, { sourceKey: this.config.id, @@ -57,8 +57,8 @@ class Connection implements EntityProviderConnection { return; } - this.check(mutation.added); - this.check(mutation.removed); + this.check(mutation.added.map(e => e.entity)); + this.check(mutation.removed.map(e => e.entity)); await db.transaction(async tx => { await db.replaceUnprocessedEntities(tx, { sourceKey: this.config.id, @@ -125,7 +125,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { }, processTask: async item => { try { - const { id, state, unprocessedEntity, entityRef } = item; + const { id, state, unprocessedEntity, entityRef, locationKey } = item; const result = await this.orchestrator.process({ entity: unprocessedEntity, state, @@ -171,6 +171,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { errors: errorsString, relations: result.relations, deferredEntities: result.deferredEntities, + locationKey, }); }); diff --git a/plugins/catalog-backend/src/next/DefaultLocationService.test.ts b/plugins/catalog-backend/src/next/DefaultLocationService.test.ts index ae2c4e9a20..f965ec3077 100644 --- a/plugins/catalog-backend/src/next/DefaultLocationService.test.ts +++ b/plugins/catalog-backend/src/next/DefaultLocationService.test.ts @@ -48,11 +48,14 @@ describe('DefaultLocationServiceTest', () => { }, deferredEntities: [ { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - name: 'bar', + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'bar', + }, }, + locationKey: 'file:///tmp/mock.yaml', }, ], relations: [], diff --git a/plugins/catalog-backend/src/next/DefaultLocationService.ts b/plugins/catalog-backend/src/next/DefaultLocationService.ts index ceb51a952e..2820ba14b1 100644 --- a/plugins/catalog-backend/src/next/DefaultLocationService.ts +++ b/plugins/catalog-backend/src/next/DefaultLocationService.ts @@ -20,7 +20,10 @@ import { LOCATION_ANNOTATION, ORIGIN_LOCATION_ANNOTATION, } from '@backstage/catalog-model'; -import { CatalogProcessingOrchestrator } from './processing/types'; +import { + CatalogProcessingOrchestrator, + DeferredEntity, +} from './processing/types'; import { LocationService, LocationStore } from './types'; import { locationSpecToMetadataName } from './util'; @@ -73,7 +76,9 @@ export class DefaultLocationService implements LocationService { target: spec.target, }, }; - const unprocessedEntities: Entity[] = [entity]; + const unprocessedEntities: DeferredEntity[] = [ + { entity, locationKey: `${spec.type}:${spec.target}` }, + ]; const entities: Entity[] = []; const state = new Map(); // ignored while (unprocessedEntities.length) { @@ -82,7 +87,7 @@ export class DefaultLocationService implements LocationService { continue; } const processed = await this.orchestrator.process({ - entity: currentEntity, + entity: currentEntity.entity, state, }); diff --git a/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts b/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts index 762a585754..82b08f2e70 100644 --- a/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts +++ b/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts @@ -113,13 +113,17 @@ describe('DefaultLocationStore', () => { type: 'delta', removed: [], added: expect.arrayContaining([ - expect.objectContaining({ - spec: { - target: - 'https://github.com/backstage/demo/blob/master/catalog-info.yml', - type: 'url', - }, - }), + { + entity: expect.objectContaining({ + spec: { + target: + 'https://github.com/backstage/demo/blob/master/catalog-info.yml', + type: 'url', + }, + }), + locationKey: + 'url:https://github.com/backstage/demo/blob/master/catalog-info.yml', + }, ]), }); }, @@ -156,15 +160,19 @@ describe('DefaultLocationStore', () => { expect(connection.applyMutation).toHaveBeenCalledWith({ type: 'delta', added: [], - removed: expect.arrayContaining([ - expect.objectContaining({ - spec: { - target: - 'https://github.com/backstage/demo/blob/master/catalog-info.yml', - type: 'url', - }, - }), - ]), + removed: [ + { + entity: expect.objectContaining({ + spec: { + target: + 'https://github.com/backstage/demo/blob/master/catalog-info.yml', + type: 'url', + }, + }), + locationKey: + 'url:https://github.com/backstage/demo/blob/master/catalog-info.yml', + }, + ], }); }, 60_000, diff --git a/plugins/catalog-backend/src/next/DefaultLocationStore.ts b/plugins/catalog-backend/src/next/DefaultLocationStore.ts index 09de77e49f..c9ffe9a682 100644 --- a/plugins/catalog-backend/src/next/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/next/DefaultLocationStore.ts @@ -19,6 +19,7 @@ import { ConflictError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; import { v4 as uuid } from 'uuid'; import { DbLocationsRow } from './database/tables'; +import { getEntityLocationRef } from './processing/util'; import { EntityProvider, EntityProviderConnection, @@ -60,10 +61,10 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { return inner; }); - + const entity = locationSpecToLocationEntity(location); await this.connection.applyMutation({ type: 'delta', - added: [locationSpecToLocationEntity(location)], + added: [{ entity, locationKey: getEntityLocationRef(entity) }], removed: [], }); @@ -102,11 +103,11 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { await tx('locations').where({ id }).del(); return location; }); - + const entity = locationSpecToLocationEntity(deleted); await this.connection.applyMutation({ type: 'delta', added: [], - removed: [locationSpecToLocationEntity(deleted)], + removed: [{ entity, locationKey: getEntityLocationRef(entity) }], }); } @@ -124,7 +125,8 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { const locations = await this.locations(); const entities = locations.map(location => { - return locationSpecToLocationEntity(location); + const entity = locationSpecToLocationEntity(location); + return { entity, locationKey: getEntityLocationRef(entity) }; }); await this.connection.applyMutation({ diff --git a/plugins/catalog-backend/src/next/NextCatalogBuilder.ts b/plugins/catalog-backend/src/next/NextCatalogBuilder.ts index 041f07f4ed..a525dacd51 100644 --- a/plugins/catalog-backend/src/next/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/next/NextCatalogBuilder.ts @@ -49,7 +49,6 @@ import { FileReaderProcessor, GithubDiscoveryProcessor, GithubOrgReaderProcessor, - LdapOrgReaderProcessor, PlaceholderProcessor, PlaceholderResolver, UrlReaderProcessor, @@ -371,7 +370,6 @@ export class NextCatalogBuilder { BitbucketDiscoveryProcessor.fromConfig(config, { logger }), GithubDiscoveryProcessor.fromConfig(config, { logger }), GithubOrgReaderProcessor.fromConfig(config, { logger }), - LdapOrgReaderProcessor.fromConfig(config, { logger }), new UrlReaderProcessor({ reader, logger }), CodeOwnersProcessor.fromConfig(config, { logger, reader }), new AnnotateLocationEntityProcessor({ integrations }), diff --git a/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts b/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts index a7be2aef10..f3921602e2 100644 --- a/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts @@ -133,8 +133,14 @@ export class NextEntitiesCatalog implements EntitiesCatalog { }; } + const dbResponse = rows.map(e => JSON.parse(e.final_entity!)); + + const entities = dbResponse.map(e => + request?.fields ? request.fields(e) : e, + ); + return { - entities: rows.map(e => JSON.parse(e.final_entity!)), + entities, pageInfo, }; } diff --git a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts index a0db3b293f..e449e5580c 100644 --- a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts +++ b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts @@ -19,6 +19,7 @@ import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/config'; import { Knex } from 'knex'; +import { Logger } from 'winston'; import * as uuid from 'uuid'; import { DatabaseManager } from './DatabaseManager'; import { DefaultProcessingDatabase } from './DefaultProcessingDatabase'; @@ -29,12 +30,15 @@ import { } from './tables'; describe('Default Processing Database', () => { - const logger = getVoidLogger(); + const defaultLogger = getVoidLogger(); const databases = TestDatabases.create({ ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], }); - async function createDatabase(databaseId: TestDatabaseId) { + async function createDatabase( + databaseId: TestDatabaseId, + logger: Logger = defaultLogger, + ) { const knex = await databases.init(databaseId); await DatabaseManager.createDatabase(knex); return { @@ -57,6 +61,123 @@ describe('Default Processing Database', () => { await db('refresh_state').insert(ref); }; + describe('addUprocessedEntities', () => { + function mockEntity(name: string, type: string): Entity { + return { + apiVersion: '1', + kind: 'Component', + metadata: { + name, + }, + spec: { + type, + }, + }; + } + + it.each(databases.eachSupportedId())( + 'updates refresh state with varying location keys, %p', + async databaseId => { + const mockWarn = jest.fn(); + const { db } = await createDatabase(databaseId, ({ + debug: jest.fn(), + warn: mockWarn, + } as unknown) as Logger); + await db.transaction(async tx => { + const knexTx = tx as Knex.Transaction; + + const steps = [ + { + locationKey: undefined, + expectedLocationKey: null, + type: 'a', + expectedType: 'a', + }, + { + locationKey: undefined, + expectedLocationKey: null, + type: 'b', + expectedType: 'b', + }, + { + locationKey: 'x', + expectedLocationKey: 'x', + type: 'c', + expectedType: 'c', + }, + { + locationKey: 'y', + expectedLocationKey: 'x', + type: 'd', + expectedType: 'c', + expectConflict: true, + }, + { + locationKey: undefined, + expectedLocationKey: 'x', + type: 'e', + expectedType: 'c', + expectConflict: true, + }, + { + locationKey: 'x', + expectedLocationKey: 'x', + type: 'f', + expectedType: 'f', + }, + ]; + for (const step of steps) { + mockWarn.mockClear(); + + await db.addUnprocessedEntities(tx, { + sourceKey: 'testing', + entities: [ + { + entity: mockEntity('1', step.type), + locationKey: step.locationKey, + }, + ], + }); + + if (step.expectConflict) { + // eslint-disable-next-line jest/no-conditional-expect + expect(mockWarn).toHaveBeenCalledWith( + expect.stringMatching(/^Detected conflicting entityRef/), + ); + } else { + // eslint-disable-next-line jest/no-conditional-expect + expect(mockWarn).not.toHaveBeenCalled(); + } + + const entities = await knexTx( + 'refresh_state', + ).select(); + expect(entities).toEqual([ + expect.objectContaining({ + entity_ref: 'component:default/1', + location_key: step.expectedLocationKey, + }), + ]); + const entity = JSON.parse(entities[0].unprocessed_entity) as Entity; + expect(entity.spec?.type).toEqual(step.expectedType); + + await expect( + knexTx( + 'refresh_state_references', + ).select(), + ).resolves.toEqual([ + expect.objectContaining({ + source_key: 'testing', + target_entity_ref: 'component:default/1', + }), + ]); + } + }); + }, + 60_000, + ); + }); + describe('updateProcessedEntity', () => { let id: string; let processedEntity: Entity; @@ -77,7 +198,7 @@ describe('Default Processing Database', () => { }); it.each(databases.eachSupportedId())( - 'fails when there is no processing state for the entity, %p', + 'fails when an entity is processed with a different locationKey, %p', async databaseId => { const { db } = await createDatabase(databaseId); await db.transaction(async tx => { @@ -89,12 +210,55 @@ describe('Default Processing Database', () => { relations: [], deferredEntities: [], }), - ).rejects.toThrow(`Processing state not found for ${id}`); + ).rejects.toThrow( + `Conflicting write of processing result for ${id} with location key 'undefined'`, + ); }); }, 60_000, ); + it.each(databases.eachSupportedId())( + 'fails when the locationKey is different, %p', + async databaseId => { + const options = { + id, + processedEntity, + state: new Map(), + relations: [], + deferredEntities: [], + locationKey: 'key', + errors: "['something broke']", + }; + const { knex, db } = await createDatabase(databaseId); + await insertRefreshStateRow(knex, { + entity_id: id, + entity_ref: 'location:default/fakelocation', + unprocessed_entity: '{}', + processed_entity: '{}', + location_key: 'key', + errors: '[]', + next_update_at: '2021-04-01 13:37:00', + last_discovery_at: '2021-04-01 13:37:00', + }); + await db.transaction(tx => db.updateProcessedEntity(tx, options)); + + const entities = await knex( + 'refresh_state', + ).select(); + expect(entities.length).toBe(1); + + await db.transaction(tx => + expect( + db.updateProcessedEntity(tx, { ...options, locationKey: 'fail' }), + ).rejects.toThrow( + `Conflicting write of processing result for ${id} with location key 'fail'`, + ), + ); + }, + 60_000, + ); + it.each(databases.eachSupportedId())( 'updates the refresh state entry with the cache, processed entity and errors, %p', async databaseId => { @@ -119,6 +283,7 @@ describe('Default Processing Database', () => { state, relations: [], deferredEntities: [], + locationKey: 'key', errors: "['something broke']", }), ); @@ -132,6 +297,7 @@ describe('Default Processing Database', () => { ); expect(entities[0].cache).toEqual(JSON.stringify(state)); expect(entities[0].errors).toEqual("['something broke']"); + expect(entities[0].location_key).toEqual('key'); }, 60_000, ); @@ -206,11 +372,14 @@ describe('Default Processing Database', () => { const deferredEntities = [ { - apiVersion: '1', - kind: 'Location', - metadata: { - name: 'next', + entity: { + apiVersion: '1', + kind: 'Location', + metadata: { + name: 'next', + }, }, + locationKey: 'mock', }, ]; @@ -227,7 +396,7 @@ describe('Default Processing Database', () => { const refreshStateEntries = await knex( 'refresh_state', ) - .where({ entity_ref: stringifyEntityRef(deferredEntities[0]) }) + .where({ entity_ref: stringifyEntityRef(deferredEntities[0].entity) }) .select(); expect(refreshStateEntries).toHaveLength(1); @@ -297,12 +466,15 @@ describe('Default Processing Database', () => { sourceKey: 'config', items: [ { - apiVersion: '1.0.0', - metadata: { - name: 'new-root', - }, - kind: 'Location', - } as Entity, + entity: { + apiVersion: '1.0.0', + metadata: { + name: 'new-root', + }, + kind: 'Location', + } as Entity, + locationKey: 'file:///tmp/foobar', + }, ], }), ); @@ -411,12 +583,15 @@ describe('Default Processing Database', () => { sourceKey: 'config', items: [ { - apiVersion: '1.0.0', - metadata: { - name: 'new-root', - }, - kind: 'Location', - } as Entity, + entity: { + apiVersion: '1.0.0', + metadata: { + name: 'new-root', + }, + kind: 'Location', + } as Entity, + locationKey: 'file:/tmp/foobar', + }, ], }); }); @@ -510,12 +685,15 @@ describe('Default Processing Database', () => { removed: [], added: [ { - apiVersion: '1.0.0', - metadata: { - name: 'new-root', - }, - kind: 'Location', - } as Entity, + entity: { + apiVersion: '1.0.0', + metadata: { + name: 'new-root', + }, + kind: 'Location', + } as Entity, + locationKey: 'file:///tmp/foobar', + }, ], }); }); @@ -614,12 +792,15 @@ describe('Default Processing Database', () => { added: [], removed: [ { - apiVersion: '1.0.0', - metadata: { - name: 'new-root', - }, - kind: 'Location', - } as Entity, + entity: { + apiVersion: '1.0.0', + metadata: { + name: 'new-root', + }, + kind: 'Location', + } as Entity, + locationKey: 'file:/tmp/foobar', + }, ], }); }); @@ -648,6 +829,73 @@ describe('Default Processing Database', () => { }, 60_000, ); + + it.each(databases.eachSupportedId())( + 'should update the location key during full replace, %p', + async databaseId => { + const { knex, db } = await createDatabase(databaseId); + await createLocations(knex, ['location:default/removed']); + await insertRefreshStateRow(knex, { + entity_id: uuid.v4(), + entity_ref: 'location:default/replaced', + unprocessed_entity: '{}', + processed_entity: '{}', + errors: '[]', + next_update_at: '2021-04-01 13:37:00', + last_discovery_at: '2021-04-01 13:37:00', + location_key: 'file:///tmp/old', + }); + + await insertRefRow(knex, { + source_key: 'lols', + target_entity_ref: 'location:default/removed', + }); + await insertRefRow(knex, { + source_key: 'lols', + target_entity_ref: 'location:default/replaced', + }); + + await db.transaction(async tx => { + await db.replaceUnprocessedEntities(tx, { + type: 'full', + sourceKey: 'lols', + items: [ + { + entity: { + apiVersion: '1.0.0', + metadata: { + name: 'replaced', + }, + kind: 'Location', + } as Entity, + locationKey: 'file:///tmp/foobar', + }, + ], + }); + }); + + const currentRefreshState = await knex( + 'refresh_state', + ).select(); + expect(currentRefreshState).toEqual([ + expect.objectContaining({ + entity_ref: 'location:default/replaced', + location_key: 'file:///tmp/foobar', + }), + ]); + + const currentRefRowState = await knex( + 'refresh_state_references', + ).select(); + expect(currentRefRowState).toEqual([ + expect.objectContaining({ + source_key: 'lols', + target_entity_ref: 'location:default/replaced', + }), + ]); + }, + 60_000, + ); }); describe('getProcessableEntities', () => { diff --git a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts index 79daee13e6..1f1ea4bf84 100644 --- a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts @@ -16,12 +16,13 @@ import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/config'; -import { ConflictError, NotFoundError } from '@backstage/errors'; +import { ConflictError } from '@backstage/errors'; import { Knex } from 'knex'; import lodash from 'lodash'; import { v4 as uuid } from 'uuid'; import type { Logger } from 'winston'; import { Transaction } from '../../database'; +import { DeferredEntity } from '../processing/types'; import { DbRefreshStateReferencesRow, DbRefreshStateRow, @@ -63,23 +64,34 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { errors, relations, deferredEntities, + locationKey, } = options; - const refreshResult = await tx('refresh_state') .update({ processed_entity: JSON.stringify(processedEntity), cache: JSON.stringify(state), errors, + location_key: locationKey, }) - .where('entity_id', id); + .where('entity_id', id) + .andWhere(inner => { + if (!locationKey) { + return inner.whereNull('location_key'); + } + return inner + .where('location_key', locationKey) + .orWhereNull('location_key'); + }); if (refreshResult === 0) { - throw new NotFoundError(`Processing state not found for ${id}`); + throw new ConflictError( + `Conflicting write of processing result for ${id} with location key '${locationKey}'`, + ); } // Schedule all deferred entities for future processing. await this.addUnprocessedEntities(tx, { entities: deferredEntities, - entityRef: stringifyEntityRef(processedEntity), + sourceEntityRef: stringifyEntityRef(processedEntity), }); // Update fragments @@ -129,32 +141,51 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { private async createDelta( tx: Knex.Transaction, options: ReplaceUnprocessedEntitiesOptions, - ): Promise<{ toAdd: Entity[]; toRemove: string[] }> { + ): Promise<{ toAdd: DeferredEntity[]; toRemove: string[] }> { if (options.type === 'delta') { return { toAdd: options.added, - toRemove: options.removed.map(e => stringifyEntityRef(e)), + toRemove: options.removed.map(e => stringifyEntityRef(e.entity)), }; } + // Grab all of the existing references from the same source, and their locationKeys as well const oldRefs = await tx( 'refresh_state_references', ) .where({ source_key: options.sourceKey }) - .select('target_entity_ref') - .then(rows => rows.map(r => r.target_entity_ref)); + .leftJoin('refresh_state', { + target_entity_ref: 'entity_ref', + }) + .select(['target_entity_ref', 'location_key']); - const items = options.items.map(entity => ({ - entity, - ref: stringifyEntityRef(entity), + const items = options.items.map(deferred => ({ + deferred, + ref: stringifyEntityRef(deferred.entity), })); - const oldRefsSet = new Set(oldRefs); + const oldRefsSet = new Map( + oldRefs.map(r => [r.target_entity_ref, r.location_key]), + ); const newRefsSet = new Set(items.map(item => item.ref)); - const toAdd = items.filter(item => !oldRefsSet.has(item.ref)); - const toRemove = oldRefs.filter(ref => !newRefsSet.has(ref)); - return { toAdd: toAdd.map(({ entity }) => entity), toRemove }; + const toAdd = new Array(); + const toRemove = oldRefs + .map(row => row.target_entity_ref) + .filter(ref => !newRefsSet.has(ref)); + + for (const item of items) { + if (!oldRefsSet.has(item.ref)) { + // Add any entity that does not exist in the database + toAdd.push(item.deferred); + } else if (oldRefsSet.get(item.ref) !== item.deferred.locationKey) { + // Remove and then re-add any entity that exists, but with a different location key + toRemove.push(item.ref); + toAdd.push(item.deferred); + } + } + + return { toAdd, toRemove }; } async replaceUnprocessedEntities( @@ -281,28 +312,10 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { } if (toAdd.length) { - const state: Knex.DbRecord[] = toAdd.map(entity => ({ - entity_id: uuid(), - entity_ref: stringifyEntityRef(entity), - unprocessed_entity: JSON.stringify(entity), - errors: '', - next_update_at: tx.fn.now(), - last_discovery_at: tx.fn.now(), - })); - - const stateReferences: DbRefreshStateReferencesRow[] = toAdd.map( - entity => ({ - source_key: options.sourceKey, - target_entity_ref: stringifyEntityRef(entity), - }), - ); - // TODO(freben): Concurrency? If we did these one by one, a .onConflict().merge would have made sense - await tx.batchInsert('refresh_state', state, BATCH_SIZE); - await tx.batchInsert( - 'refresh_state_references', - stateReferences, - BATCH_SIZE, - ); + await this.addUnprocessedEntities(tx, { + sourceKey: options.sourceKey, + entities: toAdd, + }); } } @@ -312,44 +325,123 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { ): Promise { const tx = txOpaque as Knex.Transaction; - const stateRows = options.entities.map( - entity => - ({ - entity_id: uuid(), - entity_ref: stringifyEntityRef(entity), - unprocessed_entity: JSON.stringify(entity), - errors: '', - next_update_at: tx.fn.now(), - last_discovery_at: tx.fn.now(), - } as Knex.DbRecord), - ); - const stateReferenceRows = stateRows.map( - stateRow => - ({ - source_entity_ref: options.entityRef, - target_entity_ref: stateRow.entity_ref, - } as Knex.DbRecord), - ); + // Keeps track of the entities that we end up inserting to update refresh_state_references afterwards + const stateReferences = new Array(); + const conflictingStateReferences = new Array(); // Upsert all of the unprocessed entities into the refresh_state table, by // their entity ref. - // TODO(freben): Can this be batched somehow? - for (const row of stateRows) { - await tx('refresh_state') - .insert(row) - .onConflict('entity_ref') - .merge(['unprocessed_entity', 'last_discovery_at']); + for (const { entity, locationKey } of options.entities) { + const entityRef = stringifyEntityRef(entity); + const serializedEntity = JSON.stringify(entity); + + // We optimistically try to update any existing refresh state first, as this is by far + // the most common case. + const refreshResult = await tx('refresh_state') + .update({ + unprocessed_entity: serializedEntity, + location_key: locationKey, + last_discovery_at: tx.fn.now(), + }) + .where('entity_ref', entityRef) + .andWhere(inner => { + if (!locationKey) { + return inner.whereNull('location_key'); + } + return inner + .where('location_key', locationKey) + .orWhereNull('location_key'); + }); + + if (refreshResult === 0) { + // In the event that we can't update an existing refresh state, we first try to insert a new row + try { + let query = tx('refresh_state').insert({ + entity_id: uuid(), + entity_ref: entityRef, + unprocessed_entity: serializedEntity, + errors: '', + location_key: locationKey, + next_update_at: tx.fn.now(), + last_discovery_at: tx.fn.now(), + }); + + // TODO(Rugvip): only tested towards Postgres and SQLite + // We have to do this because the only way to detect if there was a conflict with + // SQLite is to catch the error, while Postgres needs to ignore the conflict to not + // break the ongoing transaction. + if (tx.client.config.client !== 'sqlite3') { + query = query.onConflict('entity_ref').ignore(); + } + + const result: { /* postgres */ rowCount?: number } = await query; + if (result.rowCount === 0) { + throw new ConflictError( + 'Insert failed due to conflicting entity_ref', + ); + } + } catch (error) { + if ( + !error.message.includes('UNIQUE constraint failed') && + error.name !== 'ConflictError' + ) { + throw error; + } + // If the row can't be inserted, we have a conflict, but it could be either + // because of a conflicting locationKey or a race with another instance, so check + // whether the conflicting entity has the same entityRef but a different locationKey + const [conflictingEntity] = await tx( + 'refresh_state', + ) + .where({ entity_ref: entityRef }) + .select(); + + // If the location key matches it means we just had a race trigger, which we can safely ignore + if ( + !conflictingEntity || + conflictingEntity.location_key !== locationKey + ) { + this.options.logger.warn( + `Detected conflicting entityRef ${entityRef} already referenced by ${conflictingEntity.location_key} and now also ${locationKey}`, + ); + conflictingStateReferences.push(entityRef); + continue; + } + } + } + + // Skipped on locationKey conflict + stateReferences.push(entityRef); } - // Replace all references for the originating entity before creating new ones - await tx('refresh_state_references') - .where({ source_entity_ref: options.entityRef }) - .delete(); - await tx.batchInsert( - 'refresh_state_references', - stateReferenceRows, - BATCH_SIZE, - ); + // Replace all references for the originating entity or source and then create new ones + if ('sourceKey' in options) { + await tx('refresh_state_references') + .whereNotIn('target_entity_ref', conflictingStateReferences) + .andWhere({ source_key: options.sourceKey }) + .delete(); + await tx.batchInsert( + 'refresh_state_references', + stateReferences.map(entityRef => ({ + source_key: options.sourceKey, + target_entity_ref: entityRef, + })), + BATCH_SIZE, + ); + } else { + await tx('refresh_state_references') + .whereNotIn('target_entity_ref', conflictingStateReferences) + .andWhere({ source_entity_ref: options.sourceEntityRef }) + .delete(); + await tx.batchInsert( + 'refresh_state_references', + stateReferences.map(entityRef => ({ + source_entity_ref: options.sourceEntityRef, + target_entity_ref: entityRef, + })), + BATCH_SIZE, + ); + } } async getProcessableEntities( @@ -406,6 +498,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { ? JSON.parse(i.cache) : new Map(), errors: i.errors, + locationKey: i.location_key, } as RefreshStateItem), ), }; diff --git a/plugins/catalog-backend/src/next/database/tables.ts b/plugins/catalog-backend/src/next/database/tables.ts index 93453ba914..395694131d 100644 --- a/plugins/catalog-backend/src/next/database/tables.ts +++ b/plugins/catalog-backend/src/next/database/tables.ts @@ -29,6 +29,7 @@ export type DbRefreshStateRow = { next_update_at: string; last_discovery_at: string; // remove? errors?: string; + location_key?: string; }; export type DbRefreshStateReferencesRow = { diff --git a/plugins/catalog-backend/src/next/database/types.ts b/plugins/catalog-backend/src/next/database/types.ts index dd342126c4..78199900e4 100644 --- a/plugins/catalog-backend/src/next/database/types.ts +++ b/plugins/catalog-backend/src/next/database/types.ts @@ -17,11 +17,17 @@ import { Entity, EntityRelationSpec } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/config'; import { Transaction } from '../../database/types'; +import { DeferredEntity } from '../processing/types'; -export type AddUnprocessedEntitiesOptions = { - entityRef: string; - entities: Entity[]; -}; +export type AddUnprocessedEntitiesOptions = + | { + sourceEntityRef: string; + entities: DeferredEntity[]; + } + | { + sourceKey: string; + entities: DeferredEntity[]; + }; export type AddUnprocessedEntitiesResult = {}; @@ -31,7 +37,8 @@ export type UpdateProcessedEntityOptions = { state?: Map; errors?: string; relations: EntityRelationSpec[]; - deferredEntities: Entity[]; + deferredEntities: DeferredEntity[]; + locationKey?: string; }; export type UpdateProcessedEntityErrorsOptions = { @@ -48,6 +55,7 @@ export type RefreshStateItem = { lastDiscoveryAt: string; // remove? state: Map; errors?: string; + locationKey?: string; }; export type GetProcessableEntitiesResult = { @@ -57,24 +65,19 @@ export type GetProcessableEntitiesResult = { export type ReplaceUnprocessedEntitiesOptions = | { sourceKey: string; - items: Entity[]; + items: DeferredEntity[]; type: 'full'; } | { sourceKey: string; - added: Entity[]; - removed: Entity[]; + added: DeferredEntity[]; + removed: DeferredEntity[]; type: 'delta'; }; export interface ProcessingDatabase { transaction(fn: (tx: Transaction) => Promise): Promise; - addUnprocessedEntities( - tx: Transaction, - options: AddUnprocessedEntitiesOptions, - ): Promise; - replaceUnprocessedEntities( txOpaque: Transaction, options: ReplaceUnprocessedEntitiesOptions, diff --git a/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts index 48a40a2f6e..bb23b6afb3 100644 --- a/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts @@ -68,7 +68,6 @@ export class DefaultCatalogProcessingOrchestrator async process( request: EntityProcessingRequest, ): Promise { - // TODO: implement dryRun/eager return this.processSingleEntity(request.entity); } diff --git a/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts b/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts index 3f8594bd48..507def964a 100644 --- a/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts +++ b/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts @@ -24,7 +24,12 @@ import { import { Logger } from 'winston'; import { CatalogProcessorResult } from '../../ingestion'; import { locationSpecToLocationEntity } from '../util'; -import { getEntityOriginLocationRef, validateEntityEnvelope } from './util'; +import { DeferredEntity } from './types'; +import { + getEntityLocationRef, + getEntityOriginLocationRef, + validateEntityEnvelope, +} from './util'; /** * Helper class for aggregating all of the emitted data from processors. @@ -32,7 +37,7 @@ import { getEntityOriginLocationRef, validateEntityEnvelope } from './util'; export class ProcessorOutputCollector { private readonly errors = new Array(); private readonly relations = new Array(); - private readonly deferredEntities = new Array(); + private readonly deferredEntities = new Array(); private done = false; constructor( @@ -73,6 +78,8 @@ export class ProcessorOutputCollector { return; } + const location = stringifyLocationReference(i.location); + // Note that at this point, we have only validated the envelope part of // the entity data. Annotations are not part of that, so we have to be // defensive. If the annotations were malformed (e.g. were not a valid @@ -81,7 +88,6 @@ export class ProcessorOutputCollector { const annotations = entity.metadata.annotations || {}; if (typeof annotations === 'object' && !Array.isArray(annotations)) { const originLocation = getEntityOriginLocationRef(this.parentEntity); - const location = stringifyLocationReference(i.location); entity = { ...entity, metadata: { @@ -95,11 +101,14 @@ export class ProcessorOutputCollector { }; } - this.deferredEntities.push(entity); + this.deferredEntities.push({ entity, locationKey: location }); } else if (i.type === 'location') { - this.deferredEntities.push( - locationSpecToLocationEntity(i.location, this.parentEntity), + const entity = locationSpecToLocationEntity( + i.location, + this.parentEntity, ); + const locationKey = getEntityLocationRef(entity); + this.deferredEntities.push({ entity, locationKey }); } else if (i.type === 'relation') { this.relations.push(i.relation); } else if (i.type === 'error') { diff --git a/plugins/catalog-backend/src/next/processing/types.ts b/plugins/catalog-backend/src/next/processing/types.ts index e76e789e54..9e30d07404 100644 --- a/plugins/catalog-backend/src/next/processing/types.ts +++ b/plugins/catalog-backend/src/next/processing/types.ts @@ -27,7 +27,7 @@ export type EntityProcessingResult = ok: true; state: Map; completedEntity: Entity; - deferredEntities: Entity[]; + deferredEntities: DeferredEntity[]; relations: EntityRelationSpec[]; errors: Error[]; } @@ -39,3 +39,8 @@ export type EntityProcessingResult = export interface CatalogProcessingOrchestrator { process(request: EntityProcessingRequest): Promise; } + +export type DeferredEntity = { + entity: Entity; + locationKey?: string; +}; diff --git a/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts b/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts index e749fe712d..96af65fffb 100644 --- a/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts +++ b/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts @@ -74,6 +74,13 @@ describe('Stitcher', () => { type: 'looksAt', target_entity_ref: 'k:ns/other', }, + // handles and ignores duplicates + { + originating_entity_id: 'my-id', + source_entity_ref: 'k:ns/n', + type: 'looksAt', + target_entity_ref: 'k:ns/other', + }, ]); await stitcher.stitch(new Set(['k:ns/n'])); diff --git a/plugins/catalog-backend/src/next/stitching/Stitcher.ts b/plugins/catalog-backend/src/next/stitching/Stitcher.ts index bdfa76e5fc..0aa39f5ac8 100644 --- a/plugins/catalog-backend/src/next/stitching/Stitcher.ts +++ b/plugins/catalog-backend/src/next/stitching/Stitcher.ts @@ -22,6 +22,7 @@ import { } from '@backstage/catalog-model'; import { SerializedError } from '@backstage/errors'; import { Knex } from 'knex'; +import { uniqBy } from 'lodash'; import { v4 as uuid } from 'uuid'; import { Logger } from 'winston'; import { @@ -168,7 +169,11 @@ export class Stitcher { // TODO: entityRef is lower case and should be uppercase in the final // result - entity.relations = result + const uniqueRelationRows = uniqBy( + result, + r => `${r.relationType}:${r.relationTarget}`, + ); + entity.relations = uniqueRelationRows .filter(row => row.relationType /* exclude null row, if relevant */) .map(row => ({ type: row.relationType!, diff --git a/plugins/catalog-backend/src/next/types.ts b/plugins/catalog-backend/src/next/types.ts index c689f288aa..3b27dd7d10 100644 --- a/plugins/catalog-backend/src/next/types.ts +++ b/plugins/catalog-backend/src/next/types.ts @@ -15,6 +15,7 @@ */ import { Entity, Location, LocationSpec } from '@backstage/catalog-model'; +import { DeferredEntity } from './processing/types'; export interface LocationService { createLocation( @@ -39,8 +40,8 @@ export interface CatalogProcessingEngine { } export type EntityProviderMutation = - | { type: 'full'; entities: Entity[] } - | { type: 'delta'; added: Entity[]; removed: Entity[] }; + | { type: 'full'; entities: DeferredEntity[] } + | { type: 'delta'; added: DeferredEntity[]; removed: DeferredEntity[] }; export interface EntityProviderConnection { applyMutation(mutation: EntityProviderMutation): Promise; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 7d3a5fcbf5..76b4af7450 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -48,7 +48,6 @@ import { GithubOrgReaderProcessor, HigherOrderOperation, HigherOrderOperations, - LdapOrgReaderProcessor, LocationEntityProcessor, LocationReaders, PlaceholderProcessor, @@ -317,7 +316,6 @@ export class CatalogBuilder { BitbucketDiscoveryProcessor.fromConfig(config, { logger }), GithubDiscoveryProcessor.fromConfig(config, { logger }), GithubOrgReaderProcessor.fromConfig(config, { logger }), - LdapOrgReaderProcessor.fromConfig(config, { logger }), new UrlReaderProcessor({ reader, logger }), CodeOwnersProcessor.fromConfig(config, { logger, reader }), new LocationEntityProcessor({ integrations }), diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 4e30d2d218..d14ae5df42 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -52,7 +52,7 @@ "eslint-plugin-graphql": "^4.0.0", "msw": "^0.29.0", "supertest": "^6.1.3", - "ts-node": "^9.1.1" + "ts-node": "^10.0.0" }, "files": [ "dist" diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index abe8e988e9..2b18c2e57a 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -32,7 +32,7 @@ "dependencies": { "@backstage/catalog-client": "^0.3.14", "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.6", "@backstage/integration-react": "^0.1.4", @@ -54,8 +54,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 57cfc0d800..c7363dda54 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-react +## 0.2.5 + +### Patch Changes + +- 35a3e4e4c: Fix repetitive error reporting in EntityTypePicker +- Updated dependencies + - @backstage/core-app-api@0.1.4 + - @backstage/core-components@0.1.4 + - @backstage/integration@0.5.7 + - @backstage/catalog-client@0.3.15 + ## 0.2.4 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index e028516689..8c760a16d0 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "0.2.4", + "version": "0.2.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,12 +28,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-client": "^0.3.15", "@backstage/catalog-model": "^0.8.4", - "@backstage/core-app-api": "^0.1.3", - "@backstage/core-components": "^0.1.3", + "@backstage/core-app-api": "^0.1.4", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", - "@backstage/integration": "^0.5.6", + "@backstage/integration": "^0.5.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -45,7 +45,7 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", + "@backstage/cli": "^0.7.3", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index c14c458aaa..407f0f0f3f 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog +## 0.6.5 + +### Patch Changes + +- f423891ee: Fixed sizing of the System diagram when the rendered graph was wider than the container. +- e19283b39: Get rid of flex console warning for IconLink +- Updated dependencies + - @backstage/plugin-catalog-react@0.2.5 + - @backstage/core-components@0.1.4 + - @backstage/integration@0.5.7 + - @backstage/catalog-client@0.3.15 + ## 0.6.4 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 3c3095e369..776bac97c8 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.6.4", + "version": "0.6.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,14 +30,14 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-client": "^0.3.15", "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.6", + "@backstage/integration": "^0.5.7", "@backstage/integration-react": "^0.1.4", - "@backstage/plugin-catalog-react": "^0.2.4", + "@backstage/plugin-catalog-react": "^0.2.5", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -54,8 +54,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx index 5af4755bcb..272ad8a6d6 100644 --- a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx +++ b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx @@ -29,7 +29,7 @@ import { getEntityRelations, useEntity, } from '@backstage/plugin-catalog-react'; -import { Box, makeStyles, Typography } from '@material-ui/core'; +import { Box, makeStyles, Typography, useTheme } from '@material-ui/core'; import ZoomOutMap from '@material-ui/icons/ZoomOutMap'; import React from 'react'; import { useAsync } from 'react-use'; @@ -151,6 +151,7 @@ function RenderNode(props: DependencyGraphTypes.RenderNodeProps) { */ export function SystemDiagramCard() { const { entity } = useEntity(); + const theme = useTheme(); const currentSystemName = entity.metadata.name; const currentSystemNode = stringifyEntityRef(entity); const systemNodes = new Array<{ id: string; kind: string; name: string }>(); @@ -262,6 +263,8 @@ export function SystemDiagramCard() { nodeMargin={10} direction={DependencyGraphTypes.Direction.BOTTOM_TOP} renderNode={RenderNode} + paddingX={theme.spacing(4)} + paddingY={theme.spacing(4)} /> ', () => { namespace: 'my-namespace', }, spec: { + profile: { + displayName: 'Group A', + }, type: 'organization', }, }, @@ -64,6 +67,6 @@ describe('', () => { }, ); - expect(getByText('my-namespace/group-a')).toBeInTheDocument(); + expect(getByText('Group A')).toBeInTheDocument(); }); }); diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx index 53e55b96bf..ce09f0d268 100644 --- a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx +++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx @@ -18,6 +18,7 @@ import { RELATION_CHILD_OF, stringifyEntityRef, parseEntityRef, + GroupEntity, } from '@backstage/catalog-model'; import { catalogApiRef, @@ -107,7 +108,11 @@ function RenderNode(props: DependencyGraphTypes.RenderNodeProps) { * Dynamically generates a diagram of groups registered in the catalog. */ export function GroupsDiagram() { - const nodes = new Array<{ id: string; kind: string; name: string }>(); + const nodes = new Array<{ + id: string; + kind: string; + name: string; + }>(); const edges = new Array<{ from: string; to: string; label: string }>(); const configApi = useApi(configApiRef); @@ -141,7 +146,9 @@ export function GroupsDiagram() { nodes.push({ id: stringifyEntityRef(catalogItem), kind: catalogItem.kind, - name: formatEntityRefTitle(catalogItem, { defaultKind: 'Group' }), + name: + (catalogItem as GroupEntity).spec?.profile?.displayName || + formatEntityRefTitle(catalogItem, { defaultKind: 'Group' }), }); // Edge to parent diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 7c74f26281..8c11799ef3 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.2.4", @@ -48,8 +48,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 623ff72c49..2d8de17196 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -42,8 +42,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 446ecaf8fe..c736f082fb 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.6", "@backstage/theme": "^0.2.8", @@ -37,8 +37,8 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index 95bf5e10cb..20717aed60 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-github-actions +## 0.4.11 + +### Patch Changes + +- 316530684: Migrate from Moment to Luxon +- Updated dependencies + - @backstage/plugin-catalog-react@0.2.5 + - @backstage/core-components@0.1.4 + - @backstage/integration@0.5.7 + ## 0.4.10 ### Patch Changes diff --git a/plugins/github-actions/dev/index.tsx b/plugins/github-actions/dev/index.tsx index dfda83909b..ee761b3504 100644 --- a/plugins/github-actions/dev/index.tsx +++ b/plugins/github-actions/dev/index.tsx @@ -14,7 +14,65 @@ * limitations under the License. */ +import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { githubActionsPlugin } from '../src/plugin'; +import { EntityGithubActionsContent, githubActionsPlugin } from '../src/plugin'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { Entity } from '@backstage/catalog-model'; +import { GithubActionsApi, githubActionsApiRef } from '../src'; +import getWorkflowRunResponse from '../src/__fixtures__/get-workflow-run.json'; +import listJobsForWorkflowRunResponse from '../src/__fixtures__/list-jobs-for-workflow-run.json'; -createDevApp().registerPlugin(githubActionsPlugin).render(); +const mockEntity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'backstage', + description: 'backstage.io', + annotations: { + 'github.com/project-slug': 'backstage/backstage', + }, + }, + spec: { + lifecycle: 'production', + type: 'website', + owner: 'user:guest', + }, +}; + +const mockGithubActionsApi: GithubActionsApi = { + async downloadJobLogsForWorkflowRun() {}, + async getWorkflow() { + return {} as any; + }, + async getWorkflowRun() { + return getWorkflowRunResponse; + }, + async listJobsForWorkflowRun() { + return listJobsForWorkflowRunResponse as any; + }, + async listWorkflowRuns() { + return {} as any; + }, + async reRunWorkflow() { + return {} as any; + }, +}; + +createDevApp() + .registerApi({ + api: githubActionsApiRef, + deps: {}, + factory: () => mockGithubActionsApi, + }) + .registerPlugin(githubActionsPlugin) + .addPage({ + path: '/github-actions', + title: 'Github Actions', + element: ( + + + + ), + }) + .render(); diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 1948b8babe..9d775fa09b 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.4.10", + "version": "0.4.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,16 +33,16 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", - "@backstage/integration": "^0.5.6", - "@backstage/plugin-catalog-react": "^0.2.4", + "@backstage/integration": "^0.5.7", + "@backstage/plugin-catalog-react": "^0.2.5", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@octokit/rest": "^18.5.3", - "moment": "^2.27.0", + "luxon": "^1.27.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-lazylog": "^4.5.3", @@ -51,8 +51,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/github-actions/src/__fixtures__/get-workflow-run.json b/plugins/github-actions/src/__fixtures__/get-workflow-run.json new file mode 100644 index 0000000000..5527b356f1 --- /dev/null +++ b/plugins/github-actions/src/__fixtures__/get-workflow-run.json @@ -0,0 +1,176 @@ +{ + "id": 30433642, + "name": "Build", + "node_id": "MDEyOldvcmtmbG93IFJ1bjI2OTI4OQ==", + "check_suite_id": 42, + "check_suite_node_id": "MDEwOkNoZWNrU3VpdGU0Mg==", + "head_branch": "master", + "head_sha": "acb5820ced9479c074f688cc328bf03f341a511d", + "run_number": 562, + "event": "push", + "status": "queued", + "conclusion": null, + "workflow_id": 159038, + "url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642", + "html_url": "https://github.com/octo-org/octo-repo/actions/runs/30433642", + "pull_requests": [], + "created_at": "2020-01-22T19:33:08Z", + "updated_at": "2020-01-22T19:33:08Z", + "jobs_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/jobs", + "logs_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/logs", + "check_suite_url": "https://api.github.com/repos/octo-org/octo-repo/check-suites/414944374", + "artifacts_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/artifacts", + "cancel_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/cancel", + "rerun_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/rerun", + "workflow_url": "https://api.github.com/repos/octo-org/octo-repo/actions/workflows/159038", + "head_commit": { + "id": "acb5820ced9479c074f688cc328bf03f341a511d", + "tree_id": "d23f6eedb1e1b9610bbc754ddb5197bfe7271223", + "message": "Create linter.yaml", + "timestamp": "2020-01-22T19:33:05Z", + "author": { + "name": "Octo Cat", + "email": "octocat@github.com" + }, + "committer": { + "name": "GitHub", + "email": "noreply@github.com" + } + }, + "repository": { + "id": 1296269, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/octocat/Hello-World", + "description": "This your first repo!", + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_url": "git:github.com/octocat/Hello-World.git", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "hooks_url": "http://api.github.com/repos/octocat/Hello-World/hooks" + }, + "head_repository": { + "id": 217723378, + "node_id": "MDEwOlJlcG9zaXRvcnkyMTc3MjMzNzg=", + "name": "octo-repo", + "full_name": "octo-org/octo-repo", + "private": true, + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/octo-org/octo-repo", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/octo-org/octo-repo", + "forks_url": "https://api.github.com/repos/octo-org/octo-repo/forks", + "keys_url": "https://api.github.com/repos/octo-org/octo-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/octo-org/octo-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/octo-org/octo-repo/teams", + "hooks_url": "https://api.github.com/repos/octo-org/octo-repo/hooks", + "issue_events_url": "https://api.github.com/repos/octo-org/octo-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/octo-org/octo-repo/events", + "assignees_url": "https://api.github.com/repos/octo-org/octo-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/octo-org/octo-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/octo-org/octo-repo/tags", + "blobs_url": "https://api.github.com/repos/octo-org/octo-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/octo-org/octo-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/octo-org/octo-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/octo-org/octo-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/octo-org/octo-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/octo-org/octo-repo/languages", + "stargazers_url": "https://api.github.com/repos/octo-org/octo-repo/stargazers", + "contributors_url": "https://api.github.com/repos/octo-org/octo-repo/contributors", + "subscribers_url": "https://api.github.com/repos/octo-org/octo-repo/subscribers", + "subscription_url": "https://api.github.com/repos/octo-org/octo-repo/subscription", + "commits_url": "https://api.github.com/repos/octo-org/octo-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/octo-org/octo-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/octo-org/octo-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/octo-org/octo-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/octo-org/octo-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/octo-org/octo-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/octo-org/octo-repo/merges", + "archive_url": "https://api.github.com/repos/octo-org/octo-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/octo-org/octo-repo/downloads", + "issues_url": "https://api.github.com/repos/octo-org/octo-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/octo-org/octo-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/octo-org/octo-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octo-org/octo-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/octo-org/octo-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/octo-org/octo-repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/octo-org/octo-repo/deployments" + } +} diff --git a/plugins/github-actions/src/__fixtures__/list-jobs-for-workflow-run.json b/plugins/github-actions/src/__fixtures__/list-jobs-for-workflow-run.json new file mode 100644 index 0000000000..1c3837baa1 --- /dev/null +++ b/plugins/github-actions/src/__fixtures__/list-jobs-for-workflow-run.json @@ -0,0 +1,102 @@ +{ + "total_count": 1, + "jobs": [ + { + "id": 399444496, + "run_id": 29679449, + "run_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/29679449", + "node_id": "MDEyOldvcmtmbG93IEpvYjM5OTQ0NDQ5Ng==", + "head_sha": "f83a356604ae3c5d03e1b46ef4d1ca77d64a90b0", + "url": "https://api.github.com/repos/octo-org/octo-repo/actions/jobs/399444496", + "html_url": "https://github.com/octo-org/octo-repo/runs/399444496", + "status": "completed", + "conclusion": "success", + "started_at": "2020-01-20T17:42:40Z", + "completed_at": "2020-01-20T17:44:39Z", + "name": "build", + "steps": [ + { + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "number": 1, + "started_at": "2020-01-20T09:42:40.000-08:00", + "completed_at": "2020-01-20T09:42:41.000-08:00" + }, + { + "name": "Run actions/checkout@v2", + "status": "completed", + "conclusion": "success", + "number": 2, + "started_at": "2020-01-20T09:42:41.000-08:00", + "completed_at": "2020-01-20T09:42:45.000-08:00" + }, + { + "name": "Set up Ruby", + "status": "completed", + "conclusion": "success", + "number": 3, + "started_at": "2020-01-20T09:42:45.000-08:00", + "completed_at": "2020-01-20T09:42:45.000-08:00" + }, + { + "name": "Run actions/cache@v2", + "status": "completed", + "conclusion": "success", + "number": 4, + "started_at": "2020-01-20T09:42:45.000-08:00", + "completed_at": "2020-01-20T09:42:48.000-08:00" + }, + { + "name": "Install Bundler", + "status": "completed", + "conclusion": "success", + "number": 5, + "started_at": "2020-01-20T09:42:48.000-08:00", + "completed_at": "2020-01-20T09:42:52.000-08:00" + }, + { + "name": "Install Gems", + "status": "completed", + "conclusion": "success", + "number": 6, + "started_at": "2020-01-20T09:42:52.000-08:00", + "completed_at": "2020-01-20T09:42:53.000-08:00" + }, + { + "name": "Run Tests", + "status": "completed", + "conclusion": "success", + "number": 7, + "started_at": "2020-01-20T09:42:53.000-08:00", + "completed_at": "2020-01-20T09:42:59.000-08:00" + }, + { + "name": "Deploy to Heroku", + "status": "completed", + "conclusion": "success", + "number": 8, + "started_at": "2020-01-20T09:42:59.000-08:00", + "completed_at": "2020-01-20T09:44:39.000-08:00" + }, + { + "name": "Post actions/cache@v2", + "status": "completed", + "conclusion": "success", + "number": 16, + "started_at": "2020-01-20T09:44:39.000-08:00", + "completed_at": "2020-01-20T09:44:39.000-08:00" + }, + { + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "number": 17, + "started_at": "2020-01-20T09:44:39.000-08:00", + "completed_at": "2020-01-20T09:44:39.000-08:00" + } + ], + "check_run_url": "https://api.github.com/repos/octo-org/octo-repo/check-runs/399444496" + } + ] +} diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index 4b5da51ce8..aebf7dfa01 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -36,7 +36,7 @@ import { } from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ExternalLinkIcon from '@material-ui/icons/Launch'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import React from 'react'; import { Job, Jobs, Step } from '../../api'; import { useProjectName } from '../useProjectName'; @@ -74,8 +74,10 @@ const useStyles = makeStyles(theme => ({ })); const getElapsedTime = (start: string, end: string) => { - const diff = moment(moment(end || moment()).diff(moment(start))); - const timeElapsed = diff.format('m [minutes] s [seconds]'); + const startDate = DateTime.fromISO(start); + const endDate = end ? DateTime.fromISO(end) : DateTime.now(); + const diff = endDate.diff(startDate); + const timeElapsed = diff.toFormat(`m 'minutes' s 'seconds'`); return timeElapsed; }; diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 92b9c78c6a..b65747eaa1 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.6", @@ -38,8 +38,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index fb9d674b5f..ed4695372c 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -43,8 +43,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 0723062115..58b0aac809 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -44,8 +44,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 479fd2223d..1664831179 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.2.4", @@ -38,8 +38,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index ae1535b371..20674f92ef 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -48,8 +48,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index 2ed4fdac35..5b1d8520a3 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kafka-backend +## 0.2.7 + +### Patch Changes + +- 8ab897e01: Properly return a 404 when an unknown cluster is given +- Updated dependencies + - @backstage/backend-common@0.8.4 + ## 0.2.6 ### Patch Changes diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 617dd2d3cb..f06837a8e8 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kafka-backend", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.2", + "@backstage/backend-common": "^0.8.4", "@backstage/catalog-model": "^0.8.2", "@backstage/config": "^0.1.5", + "@backstage/errors": "^0.1.1", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -42,7 +43,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.3", "@types/jest-when": "^2.7.2", "@types/lodash": "^4.14.151", "jest-when": "^3.1.0", diff --git a/plugins/kafka-backend/src/service/router.test.ts b/plugins/kafka-backend/src/service/router.test.ts index d3f19f720f..4f458fe543 100644 --- a/plugins/kafka-backend/src/service/router.test.ts +++ b/plugins/kafka-backend/src/service/router.test.ts @@ -17,7 +17,7 @@ import request from 'supertest'; import express from 'express'; import { makeRouter, ClusterApi } from './router'; -import { getVoidLogger } from '@backstage/backend-common'; +import { errorHandler, getVoidLogger } from '@backstage/backend-common'; import { KafkaApi } from './KafkaApi'; import { when } from 'jest-when'; @@ -111,6 +111,19 @@ describe('router', () => { ); }); + it('handles unknown cluster errors correctly', async () => { + const response = await request(app.use(errorHandler())).get( + '/consumers/unknown/hey/offsets', + ); + expect(response.status).toEqual(404); + expect(response.body).toMatchObject({ + error: { + message: + 'Found no configured cluster "unknown", candidates are "dev", "prod"', + }, + }); + }); + it('handles internal error correctly', async () => { prodKafkaApi.fetchGroupOffsets.mockRejectedValue(Error('oh no')); diff --git a/plugins/kafka-backend/src/service/router.ts b/plugins/kafka-backend/src/service/router.ts index 68f3924f89..6c8bce7af3 100644 --- a/plugins/kafka-backend/src/service/router.ts +++ b/plugins/kafka-backend/src/service/router.ts @@ -18,6 +18,7 @@ import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import { Config } from '@backstage/config'; +import { NotFoundError } from '@backstage/errors'; import { KafkaApi, KafkaJsApiImpl } from './KafkaApi'; import _ from 'lodash'; import { getClusterDetails } from '../config/ClusterReader'; @@ -45,12 +46,20 @@ export const makeRouter = ( const clusterId = req.params.clusterId; const consumerId = req.params.consumerId; + const kafkaApi = kafkaApiByClusterName[clusterId]; + if (!kafkaApi) { + const candidates = Object.keys(kafkaApiByClusterName) + .map(n => `"${n}"`) + .join(', '); + throw new NotFoundError( + `Found no configured cluster "${clusterId}", candidates are ${candidates}`, + ); + } + logger.info( `Fetch consumer group ${consumerId} offsets from cluster ${clusterId}`, ); - const kafkaApi = kafkaApiByClusterName[clusterId]; - const groupOffsets = await kafkaApi.api.fetchGroupOffsets(consumerId); const groupWithTopicOffsets = await Promise.all( @@ -68,6 +77,7 @@ export const makeRouter = ( })); }), ); + res.json({ consumerId, offsets: groupWithTopicOffsets.flat() }); }); diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 9d8880720e..70e4eea486 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -34,8 +34,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 7d4149cea2..3a172766c6 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -32,7 +32,7 @@ "dependencies": { "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/plugin-kubernetes-common": "^0.1.1", @@ -50,8 +50,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 1d5415a521..fa82243a41 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -33,7 +33,7 @@ "dependencies": { "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -47,8 +47,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 4bbcd13440..8d46f381e5 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -42,8 +42,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/org/package.json b/plugins/org/package.json index 2ff0fac43b..65710f0615 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -35,8 +35,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index d56d162f03..eae99c1e0f 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -47,8 +47,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/register-component/CHANGELOG.md b/plugins/register-component/CHANGELOG.md index e151c82ce9..9ef2d76266 100644 --- a/plugins/register-component/CHANGELOG.md +++ b/plugins/register-component/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-register-component +## 0.2.18 + +### Patch Changes + +- b47fc34bc: Update "service catalog" references to "software catalog" +- Updated dependencies + - @backstage/plugin-catalog-react@0.2.5 + - @backstage/core-components@0.1.4 + ## 0.2.17 ### Patch Changes diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index cb4369ca84..30c7be5b0a 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.2.17", + "version": "0.2.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", - "@backstage/plugin-catalog-react": "^0.2.4", + "@backstage/plugin-catalog-react": "^0.2.5", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,8 +46,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx index c04ce43732..2874e77eb6 100644 --- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx @@ -59,7 +59,7 @@ describe('RegisterComponentPage', () => { , diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx index db185d111b..0cc36c98bc 100644 --- a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx @@ -37,7 +37,7 @@ describe('RegisterComponentResultDialog', () => { entities={[]} catalogRouteRef={createRouteRef({ path: '/catalog', - title: 'Service Catalog', + title: 'Software Catalog', })} />, { wrapper: Wrapper }, @@ -76,7 +76,7 @@ describe('RegisterComponentResultDialog', () => { entities={entities} catalogRouteRef={createRouteRef({ path: '/catalog', - title: 'Service Catalog', + title: 'Software Catalog', })} />, { wrapper: Wrapper }, diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 68be84b55d..14945abf50 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -48,8 +48,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 1a843931a1..7d7f4e7dbe 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend +## 0.12.4 + +### Patch Changes + +- 1627daac2: Fix `catalog:write` on windows systems +- ab5cc376f: Use new utilities from `@backstage/backend-common` for safely resolving child paths +- Updated dependencies + - @backstage/backend-common@0.8.4 + - @backstage/integration@0.5.7 + - @backstage/catalog-client@0.3.15 + ## 0.12.3 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 1ad60b7248..9520ae12da 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.12.3", + "version": "0.12.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,12 +29,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.3", - "@backstage/catalog-client": "^0.3.14", + "@backstage/backend-common": "^0.8.4", + "@backstage/catalog-client": "^0.3.15", "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.6", + "@backstage/integration": "^0.5.7", "@gitbeaker/core": "^30.2.0", "@gitbeaker/node": "^30.2.0", "@octokit/rest": "^18.5.3", @@ -64,7 +64,7 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.7.2", + "@backstage/cli": "^0.7.3", "@backstage/test-utils": "^0.1.14", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", diff --git a/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml b/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml index 7b3d58d0b3..1e81242e7a 100644 --- a/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml @@ -29,7 +29,6 @@ spec: title: Description type: string description: A description for the component - ui:autofocus: true ui:options: rows: 5 owner: diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts index 123f5e0ebb..3ce3052956 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts @@ -180,7 +180,7 @@ describe('fetch:cookiecutter', () => { }, }), ).rejects.toThrow( - /targetPath may not specify a path outside the working directory/, + /Relative path is not allowed to refer to a directory outside its parent/, ); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts index 9679adffa8..62dfc20125 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReader, resolveSafeChildPath } from '@backstage/backend-common'; import { JsonObject } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; @@ -138,12 +138,7 @@ export function createFetchCookiecutterAction(options: { // Finally move the template result into the task workspace const targetPath = ctx.input.targetPath ?? './'; - const outputPath = resolvePath(ctx.workspacePath, targetPath); - if (!outputPath.startsWith(ctx.workspacePath)) { - throw new InputError( - `Fetch action targetPath may not specify a path outside the working directory`, - ); - } + const outputPath = resolveSafeChildPath(ctx.workspacePath, targetPath); await fs.copy(resultDir, outputPath); }, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts index 2f6caf8c97..1a4428e3f0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts @@ -62,7 +62,7 @@ describe('fetch:plain', () => { }, }), ).rejects.toThrow( - /Fetch action targetPath may not specify a path outside the working directory/, + /Relative path is not allowed to refer to a directory outside its parent/, ); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts index 8f7eefb657..9cac6d5ab8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts @@ -14,9 +14,7 @@ * limitations under the License. */ -import path from 'path'; -import { UrlReader } from '@backstage/backend-common'; -import { InputError } from '@backstage/errors'; +import { UrlReader, resolveSafeChildPath } from '@backstage/backend-common'; import { ScmIntegrations } from '@backstage/integration'; import { fetchContents } from './helpers'; import { createTemplateAction } from '../../createTemplateAction'; @@ -56,12 +54,7 @@ export function createFetchPlainAction(options: { // Finally move the template result into the task workspace const targetPath = ctx.input.targetPath ?? './'; - const outputPath = path.resolve(ctx.workspacePath, targetPath); - if (!outputPath.startsWith(ctx.workspacePath)) { - throw new InputError( - `Fetch action targetPath may not specify a path outside the working directory`, - ); - } + const outputPath = resolveSafeChildPath(ctx.workspacePath, targetPath); await fetchContents({ reader, diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index ebe8a0c0ba..5a3231a133 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder +## 0.9.10 + +### Patch Changes + +- 9e60a728e: Upgrade `rjsf` to 3.0.0. +- a94587cad: Update dependencies +- Updated dependencies + - @backstage/plugin-catalog-react@0.2.5 + - @backstage/core-components@0.1.4 + - @backstage/integration@0.5.7 + - @backstage/catalog-client@0.3.15 + ## 0.9.9 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index c02b63f761..93a684c013 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.9.9", + "version": "0.9.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,21 +30,21 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-client": "^0.3.15", "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.6", + "@backstage/integration": "^0.5.7", "@backstage/integration-react": "^0.1.4", - "@backstage/plugin-catalog-react": "^0.2.4", + "@backstage/plugin-catalog-react": "^0.2.5", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "@rjsf/core": "^2.4.0", - "@rjsf/material-ui": "^2.4.0", + "@rjsf/core": "^3.0.0", + "@rjsf/material-ui": "^3.0.0", "@types/react": "^16.9", "classnames": "^2.2.6", "git-url-parse": "^11.4.4", @@ -59,12 +59,12 @@ "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4", - "use-immer": "^0.5.1", + "use-immer": "^0.6.0", "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/search/package.json b/plugins/search/package.json index 0cf27e8409..fba0b2ab3b 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -31,7 +31,7 @@ "dependencies": { "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/search-common": "^0.1.2", @@ -48,8 +48,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index c69c233a23..e577cf385d 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -47,8 +47,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 8c255c39a4..4a0b8f3eea 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -36,8 +36,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index b83fcf1669..c71b3e9177 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -48,8 +48,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index d1bfbfa9b8..6fbbba61f3 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", @@ -46,8 +46,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 5b9465a109..20602282c6 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-tech-radar +## 0.4.2 + +### Patch Changes + +- 846168da2: Add optional id prop passing it to the load API +- Updated dependencies + - @backstage/core-components@0.1.4 + ## 0.4.1 ### Patch Changes diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md index 35c510ddd3..469a12e8e4 100644 --- a/plugins/tech-radar/README.md +++ b/plugins/tech-radar/README.md @@ -82,7 +82,9 @@ import { } from '@backstage/plugin-tech-radar'; class MyOwnClient implements TechRadarApi { - async load(): Promise { + async load(id: string | undefined): Promise { + // if needed id prop can be used to fetch the correct data + const data = await fetch('https://mydata.json').then(res => res.json()); // maybe you'll need to do some data transformation here to make it look like TechRadarLoaderResponse @@ -120,3 +122,7 @@ You can use the `svgProps` option to pass custom React props to the `` elem // const { getByTestId } = render(...); // expect(getByTestId('tech-radar-svg')).toBeInTheDocument(); ``` + +### How do I support multiple radars + +The `TechRadarPage` and `TechRadarComponent` components both take an optional `id` prop which is subsequently passed to the `load` method of the API to distinguish which radar's data to load. diff --git a/plugins/tech-radar/api-report.md b/plugins/tech-radar/api-report.md index f8bcc84d3a..8b98f1e494 100644 --- a/plugins/tech-radar/api-report.md +++ b/plugins/tech-radar/api-report.md @@ -46,7 +46,7 @@ export interface RadarQuadrant { name: string; } -// @public +// @public (undocumented) export interface RadarRing { // (undocumented) color: string; @@ -68,8 +68,7 @@ export const Router: { // @public (undocumented) export interface TechRadarApi { - // (undocumented) - load: () => Promise; + load: (id: string | undefined) => Promise; } // @public (undocumented) @@ -78,17 +77,19 @@ export const techRadarApiRef: ApiRef; // @public (undocumented) export const TechRadarComponent: (props: TechRadarComponentProps) => JSX.Element; -// @public +// @public (undocumented) export interface TechRadarComponentProps { // (undocumented) height: number; // (undocumented) + id?: string; + // (undocumented) svgProps?: object; // (undocumented) width: number; } -// @public +// @public (undocumented) export interface TechRadarLoaderResponse { // (undocumented) entries: RadarEntry[]; diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index e4dd9de462..3ad8d3020f 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-radar", - "version": "0.4.1", + "version": "0.4.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -44,8 +44,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/tech-radar/src/api.ts b/plugins/tech-radar/src/api.ts index fe574fcfd1..34307a1697 100644 --- a/plugins/tech-radar/src/api.ts +++ b/plugins/tech-radar/src/api.ts @@ -23,11 +23,14 @@ export const techRadarApiRef = createApiRef({ }); export interface TechRadarApi { - // Loads the TechRadar response data to pass through to the TechRadar component - load: () => Promise; + /** + * Loads the TechRadar response data to pass through to the TechRadar component. + * Takes the id prop of the TechRadarComponent or TechRadarPage to distinguish between multiple radars if needed + */ + load: (id: string | undefined) => Promise; } -/** +/* * Types related to the Radar's visualization. */ @@ -59,7 +62,7 @@ export interface RadarEntrySnapshot { moved?: MovedState; } -/** +/* * Types related to data collection for the Radar. */ @@ -69,11 +72,12 @@ export interface TechRadarLoaderResponse { entries: RadarEntry[]; } -/** +/* * Set up the Radar as a Backstage component. */ export interface TechRadarComponentProps { + id?: string; width: number; height: number; svgProps?: object; diff --git a/plugins/tech-radar/src/components/RadarComponent.tsx b/plugins/tech-radar/src/components/RadarComponent.tsx index fe42ad8e78..faa2763f17 100644 --- a/plugins/tech-radar/src/components/RadarComponent.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.tsx @@ -27,13 +27,14 @@ import { Entry } from '../utils/types'; import { Progress } from '@backstage/core-components'; import { useApi, errorApiRef } from '@backstage/core-plugin-api'; -const useTechRadarLoader = () => { +const useTechRadarLoader = (id: string | undefined) => { const errorApi = useApi(errorApiRef); const techRadarApi = useApi(techRadarApiRef); - const { error, value, loading } = useAsync(async () => techRadarApi.load(), [ - techRadarApi, - ]); + const { error, value, loading } = useAsync( + async () => techRadarApi.load(id), + [techRadarApi], + ); useEffect(() => { if (error) { @@ -45,7 +46,7 @@ const useTechRadarLoader = () => { }; const RadarComponent = (props: TechRadarComponentProps): JSX.Element => { - const { loading, error, value: data } = useTechRadarLoader(); + const { loading, error, value: data } = useTechRadarLoader(props.id); const mapToEntries = ( loaderResponse: TechRadarLoaderResponse | undefined, diff --git a/plugins/tech-radar/src/components/RadarPage.test.tsx b/plugins/tech-radar/src/components/RadarPage.test.tsx index f4b768d369..7215fc40df 100644 --- a/plugins/tech-radar/src/components/RadarPage.test.tsx +++ b/plugins/tech-radar/src/components/RadarPage.test.tsx @@ -85,6 +85,7 @@ describe('RadarPage', () => { height: 800, svgProps: { 'data-testid': 'tech-radar-svg' }, }; + jest.spyOn(mockClient, 'load'); const { getByText, getByTestId } = await renderInTestApp( @@ -100,6 +101,30 @@ describe('RadarPage', () => { getByText('Pick the recommended technologies for your projects'), ).toBeInTheDocument(); expect(getByTestId('tech-radar-svg')).toBeInTheDocument(); + expect(mockClient.load).toBeCalledWith(undefined); + }); + + it('should call load with id', async () => { + const techRadarProps = { + width: 1200, + height: 800, + svgProps: { 'data-testid': 'tech-radar-svg' }, + id: 'myId', + }; + jest.spyOn(mockClient, 'load'); + + const { getByTestId } = await renderInTestApp( + + + + + , + ); + + await waitForElement(() => getByTestId('tech-radar-svg')); + + expect(getByTestId('tech-radar-svg')).toBeInTheDocument(); + expect(mockClient.load).toBeCalledWith('myId'); }); it('should call the errorApi if load fails', async () => { diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index d9992c8467..5f63c1b31d 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-techdocs-backend +## 0.8.5 + +### Patch Changes + +- b47fc34bc: Update "service catalog" references to "software catalog" +- Updated dependencies + - @backstage/backend-common@0.8.4 + - @backstage/techdocs-common@0.6.6 + ## 0.8.4 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 2dbfb17b23..0fda61b1cf 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.8.4", + "version": "0.8.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.3", + "@backstage/backend-common": "^0.8.4", "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/techdocs-common": "^0.6.5", + "@backstage/techdocs-common": "^0.6.6", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", "dockerode": "^3.2.1", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.2", + "@backstage/cli": "^0.7.3", "@types/dockerode": "^3.2.1", "supertest": "^6.1.3" }, diff --git a/plugins/techdocs-backend/src/DocsBuilder/builder.ts b/plugins/techdocs-backend/src/DocsBuilder/builder.ts index 46e4bd0c75..e9df0b8400 100644 --- a/plugins/techdocs-backend/src/DocsBuilder/builder.ts +++ b/plugins/techdocs-backend/src/DocsBuilder/builder.ts @@ -75,7 +75,7 @@ export class DocsBuilder { public async build(): Promise { if (!this.entity.metadata.uid) { throw new Error( - 'Trying to build documentation for entity not in service catalog', + 'Trying to build documentation for entity not in software catalog', ); } diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 94ae68a560..9e93c3a251 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs +## 0.9.8 + +### Patch Changes + +- 99a2873c7: Include cookies when making fetch requests for SVG from techdocs plugin +- a444c7431: Filter fetched entity fields to optimize loading techdocs list +- Updated dependencies + - @backstage/plugin-catalog-react@0.2.5 + - @backstage/core-components@0.1.4 + - @backstage/integration@0.5.7 + ## 0.9.7 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 2f15ffe3db..1a5abc6fbf 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.9.7", + "version": "0.9.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "dependencies": { "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.6", + "@backstage/integration": "^0.5.7", "@backstage/integration-react": "^0.1.4", - "@backstage/plugin-catalog-react": "^0.2.4", + "@backstage/plugin-catalog-react": "^0.2.5", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -52,8 +52,8 @@ "sanitize-html": "^2.3.2" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 9b106c94ce..90b968de6c 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -124,7 +124,9 @@ export const TechDocsCustomHome = ({ const configApi: ConfigApi = useApi(configApiRef); const { value: entities, loading, error } = useAsync(async () => { - const response = await catalogApi.getEntities(); + const response = await catalogApi.getEntities({ + fields: ['apiVersion', 'kind', 'metadata', 'spec.owner', 'spec.type'], + }); return response.items.filter((entity: Entity) => { return !!entity.metadata.annotations?.['backstage.io/techdocs-ref']; }); diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts index 84ac4be7ee..f04c9963f0 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts @@ -154,6 +154,9 @@ describe('addBaseUrl', () => { await waitFor(() => { const actualSrc = root.getElementById('x')?.getAttribute('src'); expect(expectedSrc).toEqual(actualSrc); + expect(global.fetch).toHaveBeenCalledWith(`${API_ORIGIN_URL}/test.svg`, { + credentials: 'include', + }); }); }); diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts index 81021fbde8..f21cb8c47a 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts @@ -64,7 +64,7 @@ export const addBaseUrl = ({ const apiOrigin = await techdocsStorageApi.getApiOrigin(); if (isSvgNeedingInlining(attributeName, elemAttribute, apiOrigin)) { try { - const svg = await fetch(newValue); + const svg = await fetch(newValue, { credentials: 'include' }); const svgContent = await svg.text(); elem.setAttribute( attributeName, diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 4c86b5b524..d4a18a07b9 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.8.4", - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.2.4", @@ -40,8 +40,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", @@ -49,8 +49,8 @@ "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", - "msw": "^0.29.0", - "cross-fetch": "^3.0.6" + "cross-fetch": "^3.0.6", + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index e4720dec08..7a691594a4 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -42,8 +42,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index b99897b4bf..311498ba49 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core-components": "^0.1.3", + "@backstage/core-components": "^0.1.4", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -42,8 +42,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/core-app-api": "^0.1.3", + "@backstage/cli": "^0.7.3", + "@backstage/core-app-api": "^0.1.4", "@backstage/dev-utils": "^0.2.0", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", diff --git a/yarn.lock b/yarn.lock index 7885e5f724..dceed09a91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,10 +11,10 @@ call-me-maybe "^1.0.1" js-yaml "^3.13.1" -"@apollo/protobufjs@^1.0.3": - version "1.0.4" - resolved "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.4.tgz#cf01747a55359066341f31b5ce8db17df44244e0" - integrity sha512-EE3zx+/D/wur/JiLp6VCiw1iYdyy1lCJMf8CGPkLeDt5QJrN4N8tKFx33Ah4V30AUQzMk7Uz4IXKZ1LOj124gA== +"@apollo/protobufjs@1.2.2": + version "1.2.2" + resolved "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz#4bd92cd7701ccaef6d517cdb75af2755f049f87c" + integrity sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -30,19 +30,17 @@ "@types/node" "^10.1.0" long "^4.0.0" -"@apollographql/apollo-tools@^0.4.3": - version "0.4.8" - resolved "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.4.8.tgz#d81da89ee880c2345eb86bddb92b35291f6135ed" - integrity sha512-W2+HB8Y7ifowcf3YyPHgDI05izyRtOeZ4MqIr7LbTArtmJ0ZHULWpn84SGMW7NAvTV1tFExpHlveHhnXuJfuGA== - dependencies: - apollo-env "^0.6.5" +"@apollographql/apollo-tools@^0.5.0": + version "0.5.1" + resolved "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.1.tgz#f0baef739ff7e2fafcb8b98ad29f6ac817e53e32" + integrity sha512-ZII+/xUFfb9ezDU2gad114+zScxVFMVlZ91f8fGApMzlS1kkqoyLnC4AJaQ1Ya/X+b63I20B4Gd+eCL8QuB4sA== -"@apollographql/graphql-playground-html@1.6.26": - version "1.6.26" - resolved "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz#2f7b610392e2a872722912fc342b43cf8d641cb3" - integrity sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ== +"@apollographql/graphql-playground-html@1.6.27": + version "1.6.27" + resolved "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.27.tgz#bc9ab60e9445aa2a8813b4e94f152fa72b756335" + integrity sha512-tea2LweZvn6y6xFV11K0KC8ETjmm52mQrW+ezgB2O/aTQf8JGyFmMcRPFgUaQZeHbWdm8iisDC6EjOKsXu0nfw== dependencies: - xss "^1.0.6" + xss "^1.0.8" "@apollographql/graphql-upload-8-fork@^8.1.3": version "8.1.3" @@ -1285,14 +1283,6 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime-corejs2@^7.8.7": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.14.0.tgz#5519b92ccc819bd3d0a1ecb25ab3cb5a775485f8" - integrity sha512-btR4E8JiGlmmDI5YgirlG9z3T91rBdxnVh2YuEStrHDcekffaaIeK+CE0S4IaYUyYhMa7rFDfF2GEO79XNbLEA== - dependencies: - core-js "^2.6.5" - regenerator-runtime "^0.13.4" - "@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.11.2", "@babel/runtime-corejs3@^7.13.17": version "7.14.0" resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.0.tgz#6bf5fbc0b961f8e3202888cb2cd0fb7a0a9a3f66" @@ -1370,32 +1360,6 @@ "@babel/helper-validator-identifier" "^7.14.0" to-fast-properties "^2.0.0" -"@backstage/catalog-model@^0.7.4": - version "0.8.4" - dependencies: - "@backstage/config" "^0.1.5" - "@backstage/errors" "^0.1.1" - "@types/json-schema" "^7.0.5" - "@types/yup" "^0.29.8" - ajv "^7.0.3" - json-schema "^0.3.0" - lodash "^4.17.15" - uuid "^8.0.0" - yup "^0.29.3" - -"@backstage/catalog-model@^0.7.9": - version "0.8.4" - dependencies: - "@backstage/config" "^0.1.5" - "@backstage/errors" "^0.1.1" - "@types/json-schema" "^7.0.5" - "@types/yup" "^0.29.8" - ajv "^7.0.3" - json-schema "^0.3.0" - lodash "^4.17.15" - uuid "^8.0.0" - yup "^0.29.3" - "@backstage/core-api@^0.2.23": version "0.2.23" resolved "https://registry.npmjs.org/@backstage/core-api/-/core-api-0.2.23.tgz#c0ec13407ff7c78d376eb18e9d8e1490d54d995b" @@ -1414,7 +1378,7 @@ react-use "^17.2.4" zen-observable "^0.8.15" -"@backstage/core@*", "@backstage/core@^0.7.11", "@backstage/core@^0.7.3", "@backstage/core@^0.7.9": +"@backstage/core@*": version "0.7.14" resolved "https://registry.npmjs.org/@backstage/core/-/core-0.7.14.tgz#863844fe40bb6a29bcc2d297e42055633b0e886f" integrity sha512-W7EMspBXrp1GPALK6+qdJjsvkqcaYGFyoh8/bRAXABIkJpGQGiy4xUZUKDoMhd+OdVHv/mzyyv3fH2yc32J07w== @@ -1460,48 +1424,6 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" -"@backstage/plugin-catalog-react@^0.1.4": - version "0.1.6" - resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.1.6.tgz#1d72756cce22b880987c30ac27e23af8efa22843" - integrity sha512-/60GIrcFfyAiKn3bylXegwtHf1NQxQa4PcXD7nSIB5ISCuot2mkI32LMjciEvNHR7efGA5Yv6yWelFSFdvbcKQ== - dependencies: - "@backstage/catalog-client" "^0.3.11" - "@backstage/catalog-model" "^0.7.9" - "@backstage/core" "^0.7.9" - "@material-ui/core" "^4.11.0" - "@types/react" "^16.9" - lodash "^4.17.15" - react "^16.13.1" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^17.2.4" - -"@backstage/plugin-catalog@^0.5.1": - version "0.6.4" - dependencies: - "@backstage/catalog-client" "^0.3.14" - "@backstage/catalog-model" "^0.8.4" - "@backstage/core-components" "^0.1.3" - "@backstage/core-plugin-api" "^0.1.3" - "@backstage/errors" "^0.1.1" - "@backstage/integration" "^0.5.6" - "@backstage/integration-react" "^0.1.4" - "@backstage/plugin-catalog-react" "^0.2.4" - "@backstage/theme" "^0.2.8" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - classnames "^2.2.6" - git-url-parse "^11.4.4" - lodash "^4.17.21" - react "^16.13.1" - react-dom "^16.13.1" - react-helmet "6.1.0" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^17.2.4" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2754,6 +2676,11 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@josephg/resolvable@^1.0.0": + version "1.0.1" + resolved "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb" + integrity sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg== + "@jsdevtools/ono@^7.1.3": version "7.1.3" resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" @@ -3496,7 +3423,7 @@ resolved "https://registry.npmjs.org/@material-icons/font/-/font-1.0.3.tgz#f722e5a69a03f20ef47d015cb69420bebeeaabe5" integrity sha512-aIRd0Z9b/HJ/O24KOaP7dNsXypMnXhpWrpLVYvQB/JesVqXYSbioYND200sR+C14a0LSCp+qWnWCnSXmN1hWGw== -"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1": +"@material-ui/core@^4.11.0": version "4.11.3" resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.11.3.tgz#f22e41775b0bd075e36a7a093d43951bf7f63850" integrity sha512-Adt40rGW6Uds+cAyk3pVgcErpzU/qxc7KBR94jFHBYretU4AtWZltYcNsbeMn9tXL86jjVL1kuGcIHsgLgFGRw== @@ -3514,7 +3441,25 @@ react-is "^16.8.0 || ^17.0.0" react-transition-group "^4.4.0" -"@material-ui/icons@^4.9.1": +"@material-ui/core@^4.11.3": + version "4.11.4" + resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.11.4.tgz#4fb9fe5dec5dcf780b687e3a40cff78b2b9640a4" + integrity sha512-oqb+lJ2Dl9HXI9orc6/aN8ZIAMkeThufA5iZELf2LQeBn2NtjVilF5D2w7e9RpntAzDb4jK5DsVhkfOvFY/8fg== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/styles" "^4.11.4" + "@material-ui/system" "^4.11.3" + "@material-ui/types" "5.1.0" + "@material-ui/utils" "^4.11.2" + "@types/react-transition-group" "^4.2.0" + clsx "^1.0.4" + hoist-non-react-statics "^3.3.2" + popper.js "1.16.1-lts" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + react-transition-group "^4.4.0" + +"@material-ui/icons@^4.11.2", "@material-ui/icons@^4.9.1": version "4.11.2" resolved "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz#b3a7353266519cd743b6461ae9fdfcb1b25eb4c5" integrity sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ== @@ -3532,6 +3477,17 @@ prop-types "^15.7.2" react-is "^16.8.0" +"@material-ui/lab@4.0.0-alpha.57": + version "4.0.0-alpha.57" + resolved "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.57.tgz#e8961bcf6449e8a8dabe84f2700daacfcafbf83a" + integrity sha512-qo/IuIQOmEKtzmRD2E4Aa6DB4A87kmY6h0uYhjUmrrgmEAgbbw9etXpWPVXuRK6AGIQCjFzV6WO2i21m1R4FCw== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/utils" "^4.11.2" + clsx "^1.0.4" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + "@material-ui/pickers@^3.2.2": version "3.2.10" resolved "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae" @@ -3556,7 +3512,7 @@ react-transition-group "^4.0.0" rifm "^0.7.0" -"@material-ui/styles@^4.10.0", "@material-ui/styles@^4.11.0", "@material-ui/styles@^4.11.3", "@material-ui/styles@^4.9.6": +"@material-ui/styles@^4.10.0", "@material-ui/styles@^4.11.0", "@material-ui/styles@^4.11.3", "@material-ui/styles@^4.11.4", "@material-ui/styles@^4.9.6": version "4.11.4" resolved "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz#eb9dfccfcc2d208243d986457dff025497afa00d" integrity sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew== @@ -3610,19 +3566,28 @@ "@types/whatwg-streams" "^0.0.7" "@microsoft/api-documenter@^7.12.16": - version "7.12.21" - resolved "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.12.21.tgz#cdafcd4575d42e55eb8ca338a3a5fb868f9d5872" - integrity sha512-XZQKnMppgkTHeGh92fQ7kidT2Bxr3o775Mo9gP21+kTQs3LnvukHmSnFwBTHTXpvOq1wnkbT430dRvxdayWECQ== + version "7.13.24" + resolved "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.13.24.tgz#3bae26f7520a993c4009569d8bd229c76d266dc3" + integrity sha512-HH8aE7Yo0D+eQuV86lwF4YGW669wNxUevybGMndsTyQEeRqUpnr4wz9ptFi7FGjaUjTKjGWQKH2bm4dt8BZsUg== dependencies: - "@microsoft/api-extractor-model" "7.12.5" - "@microsoft/tsdoc" "0.12.24" - "@rushstack/node-core-library" "3.36.2" + "@microsoft/api-extractor-model" "7.13.3" + "@microsoft/tsdoc" "0.13.2" + "@rushstack/node-core-library" "3.39.0" "@rushstack/ts-command-line" "4.7.10" colors "~1.2.1" js-yaml "~3.13.1" resolve "~1.17.0" -"@microsoft/api-extractor-model@7.12.5", "@microsoft/api-extractor-model@^7.12.5", "@microsoft/api-extractor-model@workspace:*": +"@microsoft/api-extractor-model@7.13.3", "@microsoft/api-extractor-model@^7.12.5": + version "7.13.3" + resolved "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.13.3.tgz#ac01c064c5af520d3661c85d7e5ef95e1ca8ab92" + integrity sha512-uXilAhu2GcvyY/0NwVRk3AN7TFYjkPnjHLV2UywTTz9uglS+Af0YjNrCy+aaK8qXtfbFWdBzkH9N2XU8/YBeRQ== + dependencies: + "@microsoft/tsdoc" "0.13.2" + "@microsoft/tsdoc-config" "~0.15.2" + "@rushstack/node-core-library" "3.39.0" + +"@microsoft/api-extractor-model@workspace:*": version "7.12.5" resolved "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.12.5.tgz#28d2804865ceba9cd89ab4f05cff99d16fa6c9b8" integrity sha512-oeHZW83JWjIVoCDvdwI5nsZGPxThbq4gZTLAYNeJGZE/mKEO0iayMPGmI3EllJBjwQsFvNVU+O/HGULhB2to/g== @@ -3657,11 +3622,26 @@ resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-1.25.0.tgz#1f543ebc029a115dd1d48a1ae99d7ddd5ee9af57" integrity sha512-RsuA+ROaU3voWzG9TVBkRKxmLatteRGduFDi5p0k3FUHho49rm9SvrA7DUyYbSXLy2xXRx9AnjKM9klYBeKEiQ== +"@microsoft/tsdoc-config@~0.15.2": + version "0.15.2" + resolved "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.15.2.tgz#eb353c93f3b62ab74bdc9ab6f4a82bcf80140f14" + integrity sha512-mK19b2wJHSdNf8znXSMYVShAHktVr/ib0Ck2FA3lsVBSEhSI/TfXT7DJQkAYgcztTuwazGcg58ZjYdk0hTCVrA== + dependencies: + "@microsoft/tsdoc" "0.13.2" + ajv "~6.12.6" + jju "~1.4.0" + resolve "~1.19.0" + "@microsoft/tsdoc@0.12.24": version "0.12.24" resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz#30728e34ebc90351dd3aff4e18d038eed2c3e098" integrity sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg== +"@microsoft/tsdoc@0.13.2": + version "0.13.2" + resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz#3b0efb6d3903bd49edb073696f60e90df08efb26" + integrity sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg== + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -4092,40 +4072,40 @@ prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" -"@rjsf/core@^2.4.0": - version "2.5.1" - resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.5.1.tgz#95a842d22bab5f83929662fcd73739108e9f5cbb" - integrity sha512-km8NYScXNONaL5BiSLS6wyDj49pOLZtn0iXg7Zxlm921uuf3o2AAX5SuZS5kB4Zj2zlrVMrXESexfX6bxdDYHw== +"@rjsf/core@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@rjsf/core/-/core-3.0.0.tgz#7f79b0635c969e3fbfe0aaa4773d844fbee68a63" + integrity sha512-QSvyVMDiwd7neVnIMOte4tZUvtDWgLDYKOsq1tgCsIFh+RXskd3AEBf6IsK8w6Wg36Om7YalDqqrtXbk9z+rLw== dependencies: - "@babel/runtime-corejs2" "^7.8.7" - "@types/json-schema" "^7.0.4" + "@types/json-schema" "^7.0.7" ajv "^6.7.0" - core-js "^2.5.7" + core-js-pure "^3.6.5" json-schema-merge-allof "^0.6.0" jsonpointer "^4.0.1" lodash "^4.17.15" + nanoid "^3.1.23" prop-types "^15.7.2" - react-app-polyfill "^1.0.4" react-is "^16.9.0" - shortid "^2.2.14" -"@rjsf/material-ui@^2.4.0": - version "2.5.1" - resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.5.1.tgz#b93ac9f1f4a909e2aae729616859c2d72557e53e" - integrity sha512-ooKxQJO12+i1xCGtknMZDxWSbVlSEgQ5U1I7lJ+uI/MvwAg3Rz9wb2cTEF3ErBczT7EEW+j1lR19rxBjFd78MA== +"@rjsf/material-ui@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-3.0.0.tgz#69ece2cb549f0e860b5f89898db90edcc95b15ba" + integrity sha512-T2B8QnrDQphbFNxDz7baAa0zTd5TXJmO9soHBPTKKdniRbMEOQ19AJBbZkA3ED2XZa/xrUY/6XjERQLpNACddw== -"@roadiehq/backstage-plugin-buildkite@^1.0.3": - version "1.0.3" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-1.0.3.tgz#d865da83380e1cdcec5825f856597446f5652f53" - integrity sha512-HMDworpWRgwhmQETAe2DUX3cxddP+N1q8bTWxAQwOHO6mecF08uvZDGA1VdWabXSPpJwFUiZEi9OLlNYkl32GA== +"@roadiehq/backstage-plugin-buildkite@^1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-1.0.4.tgz#8bd89d235c12b0093dd8c35d5397d8332bb62df0" + integrity sha512-SkPJzkLhVo1vMM76EShJRRQegXFKkY1bf1TMVIqR2Cm3E9QXHyN099+dSimFW8f5uyNioEHuRkSh7Iwm7AZekA== dependencies: - "@backstage/catalog-model" "^0.8.0" - "@backstage/core" "^0.7.11" - "@backstage/plugin-catalog-react" "^0.2.0" - "@backstage/theme" "^0.2.7" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" + "@backstage/catalog-model" "^0.8.4" + "@backstage/core-app-api" "^0.1.3" + "@backstage/core-components" "^0.1.3" + "@backstage/core-plugin-api" "^0.1.3" + "@backstage/plugin-catalog-react" "^0.2.4" + "@backstage/theme" "^0.2.6" + "@material-ui/core" "^4.11.3" + "@material-ui/icons" "^4.11.2" + "@material-ui/lab" "4.0.0-alpha.57" history "^5.0.0" moment "^2.29.1" react "^16.13.1" @@ -4135,13 +4115,15 @@ react-router-dom "6.0.0-beta.0" react-use "^17.2.4" -"@roadiehq/backstage-plugin-github-insights@^1.1.11": - version "1.1.11" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-1.1.11.tgz#6f716a0864b7e3a7255d7ec97c92a68a99874025" - integrity sha512-fDW92aCGXJtZ36TLgEkHiLCZgNKh5lqRFSmStd+58o4QRMiJp852Tcbi2ySrtf03SnGKn7rvD2CqThx/VcwPwg== +"@roadiehq/backstage-plugin-github-insights@^1.1.15": + version "1.1.15" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-1.1.15.tgz#ef033f85c54f5203a2bc161290f46e861dc6feca" + integrity sha512-UH8lvDZd1OVP6fX+mF0eTrxqo/QmGWiCWvxumxkyU61sF3XkXzEqhQDKlWWEtpGLPa++yf98p00wfSeXRfjRWA== dependencies: - "@backstage/catalog-model" "^0.8.0" - "@backstage/core" "^0.7.11" + "@backstage/catalog-model" "^0.8.4" + "@backstage/core-app-api" "^0.1.3" + "@backstage/core-components" "^0.1.3" + "@backstage/core-plugin-api" "^0.1.3" "@backstage/plugin-catalog-react" "^0.2.0" "@backstage/theme" "^0.2.7" "@date-io/core" "2.10.7" @@ -4151,18 +4133,20 @@ "@octokit/rest" "^18.5.3" "@octokit/types" "^6.14.2" history "^5.0.0" - react "^16.13.1" - react-dom "^16.13.1" + react "^16.12.0" + react-dom "^16.12.0" react-router "^6.0.0-beta.0" react-use "^17.2.4" -"@roadiehq/backstage-plugin-github-pull-requests@^1.0.5": - version "1.0.5" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-1.0.5.tgz#7b71823b1c39dbcb89276a5725815c15a0e27943" - integrity sha512-be+buEb4tkMfVB1qFn3OwFc4pq9/Nu+5C5LsdEwhRnJHN7eLcOBMnhH5l0zAYkHErpF3zbI3xngbap+TATbm0Q== +"@roadiehq/backstage-plugin-github-pull-requests@^1.0.8": + version "1.0.8" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-1.0.8.tgz#7d6202ec60808156058bd2d335f7df332934ae56" + integrity sha512-g1CudaKX1NYW0wfbtjcZxFWmDMfw1IvpJD4ymmwZmsogBOHFknlowVmwwSL4W4LwK6IPypIZkOe95Vd2PrjO+Q== dependencies: - "@backstage/catalog-model" "^0.8.0" - "@backstage/core" "^0.7.11" + "@backstage/catalog-model" "^0.8.4" + "@backstage/core-app-api" "^0.1.3" + "@backstage/core-components" "^0.1.3" + "@backstage/core-plugin-api" "^0.1.3" "@backstage/plugin-catalog-react" "^0.2.0" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -4172,25 +4156,26 @@ history "^5.0.0" moment "^2.27.0" node-fetch "^2.6.1" - react "^16.13.1" - react-dom "^16.13.1" + react "^16.12.0" + react-dom "^16.12.0" react-router "6.0.0-beta.0" react-use "^17.2.4" -"@roadiehq/backstage-plugin-travis-ci@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-1.0.0.tgz#5aa29f4ba2086b6341a903a22c843d898808f503" - integrity sha512-JjMDwbou1iY0sSoit8ttpzXEP6lhmLmxf/Pj6Am/1rcia5KLdeMUqpilkzQ33LeHiMXXxu95gp4r6aJ5vC9wGg== +"@roadiehq/backstage-plugin-travis-ci@^1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-1.0.4.tgz#224d17749b6bb33fbc5bf50489d7e7a87b06aa3a" + integrity sha512-rSMB+ZuI9xXIwgqJv7NW/wt3ulZa9rSLsCzkMO30/6/TMbenmsIyfmIP9XI32D9mPew4dVX9spi6KsgEo41WHw== dependencies: - "@backstage/catalog-model" "^0.7.4" - "@backstage/core" "^0.7.3" - "@backstage/plugin-catalog" "^0.5.1" - "@backstage/plugin-catalog-react" "^0.1.4" - "@backstage/theme" "^0.2.5" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - cross-fetch "^3.0.6" + "@backstage/catalog-model" "^0.8.4" + "@backstage/core-app-api" "^0.1.3" + "@backstage/core-components" "^0.1.3" + "@backstage/core-plugin-api" "^0.1.3" + "@backstage/plugin-catalog-react" "^0.2.4" + "@backstage/theme" "^0.2.6" + "@material-ui/core" "^4.11.3" + "@material-ui/icons" "^4.11.2" + "@material-ui/lab" "4.0.0-alpha.57" + cross-fetch "^3.1.3" date-fns "^2.18.0" history "^5.0.0" moment "^2.29.1" @@ -4198,7 +4183,7 @@ react-dom "^16.13.1" react-router "6.0.0-beta.0" react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" + react-use "^17.2.4" "@rollup/plugin-commonjs@^17.1.0": version "17.1.0" @@ -4232,16 +4217,16 @@ is-module "^1.0.0" resolve "^1.19.0" -"@rollup/plugin-yaml@^2.1.1": - version "2.1.1" - resolved "https://registry.npmjs.org/@rollup/plugin-yaml/-/plugin-yaml-2.1.1.tgz#6c864adee22004eb325d53bc4ee5553828e056d8" - integrity sha512-CnGD3dbDhP+JeKFX7kJuaBOa7jVzXpl3G8lXp1hasB87cDYOz7qNWd4cFPmYbxMsT/8OCVsceIpRtQ+cj9uxBw== +"@rollup/plugin-yaml@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@rollup/plugin-yaml/-/plugin-yaml-3.0.0.tgz#632af397a6faaea10c885680c575ae01f4556c1d" + integrity sha512-nXYI3WsfG7Oq5K5k7evGQ1ZABGCPhHxMaMX4T3GRSDi9VyhkaU1+8R1RzTRFXITFJHx0QEGn8jR+P9pux5FuBQ== dependencies: - "@rollup/pluginutils" "^3.0.1" - js-yaml "^3.13.1" + "@rollup/pluginutils" "^3.1.0" + js-yaml "^3.14.0" tosource "^1.0.0" -"@rollup/pluginutils@^3.0.1", "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": +"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== @@ -4273,6 +4258,21 @@ timsort "~0.3.0" z-schema "~3.18.3" +"@rushstack/node-core-library@3.39.0": + version "3.39.0" + resolved "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.39.0.tgz#38928946d15ae89b773386cf97433d0d1ec83b93" + integrity sha512-kgu3+7/zOBkZU0+NdJb1rcHcpk3/oTjn5c8cg5nUTn+JDjEw58yG83SoeJEcRNNdl11dGX0lKG2PxPsjCokZOQ== + dependencies: + "@types/node" "10.17.13" + colors "~1.2.1" + fs-extra "~7.0.1" + import-lazy "~4.0.0" + jju "~1.4.0" + resolve "~1.17.0" + semver "~7.3.0" + timsort "~0.3.0" + z-schema "~3.18.3" + "@rushstack/rig-package@workspace:*": version "0.2.12" resolved "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.2.12.tgz#c434d62b28e0418a040938226f8913971d0424c7" @@ -5404,6 +5404,26 @@ resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1" + integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA== + "@types/accepts@*", "@types/accepts@^1.3.5": version "1.3.5" resolved "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" @@ -5629,14 +5649,7 @@ resolved "https://registry.npmjs.org/@types/core-js/-/core-js-2.5.4.tgz#fc42ebde7d9cfa7c5f2668f117449b02348e41fd" integrity sha512-Xwy8o12ak+iYgFr/KCVaVK5Sy+jFMiiPAID3+ObvMlBzy26XQJw5xu+a6rlHsrJENXj/AwJOGsJpVohUjAzSKQ== -"@types/cors@2.8.8": - version "2.8.8" - resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.8.tgz#317a8d8561995c60e35b9e0fcaa8d36660c98092" - integrity sha512-fO3gf3DxU2Trcbr75O7obVndW/X5k8rJNZkLXlQWStTHhP71PkRqjwPIEI0yMnJdg9R9OasjU+Bsr+Hr1xy/0w== - dependencies: - "@types/express" "*" - -"@types/cors@^2.8.6": +"@types/cors@2.8.10", "@types/cors@^2.8.6": version "2.8.10" resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz#61cc8469849e5bcdd0c7044122265c39cec10cf4" integrity sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ== @@ -5732,19 +5745,10 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.5": - version "4.17.19" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d" - integrity sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express-serve-static-core@4.17.18": - version "4.17.18" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40" - integrity sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA== +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18", "@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.5": + version "4.17.21" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz#a427278e106bca77b83ad85221eae709a3414d42" + integrity sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA== dependencies: "@types/node" "*" "@types/qs" "*" @@ -5765,13 +5769,13 @@ "@types/express" "*" "@types/xml2js" "*" -"@types/express@*", "@types/express@4.17.7", "@types/express@^4.17.6", "@types/express@^4.17.7": - version "4.17.7" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" - integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ== +"@types/express@*", "@types/express@^4.17.12", "@types/express@^4.17.6", "@types/express@^4.17.7": + 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" "*" + "@types/express-serve-static-core" "^4.17.18" "@types/qs" "*" "@types/serve-static" "*" @@ -5971,7 +5975,7 @@ dependencies: "@types/sizzle" "*" -"@types/js-cookie@2.2.6", "@types/js-cookie@^2.2.6": +"@types/js-cookie@^2.2.6": version "2.2.6" resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f" integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw== @@ -6067,7 +6071,7 @@ "@types/koa-compose" "*" "@types/node" "*" -"@types/ldapjs@^1.0.9": +"@types/ldapjs@^1.0.10": version "1.0.10" resolved "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-1.0.10.tgz#bac705c9e154b97d69496b5213cc28dbe9715a37" integrity sha512-AMkMxkK/wjYtWebNH2O+rARfo7scBpW3T23g6zmGCwDgbyDbR79XWpcSqhPWdU+fChaF+I3dVyl9X2dT1CyI9w== @@ -6130,10 +6134,10 @@ resolved "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73" integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM= -"@types/mime@*": - version "2.0.1" - resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" - integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== "@types/mini-css-extract-plugin@^1.2.2": version "1.2.2" @@ -6180,14 +6184,6 @@ dependencies: "@types/node" "*" -"@types/node-fetch@2.5.7": - version "2.5.7" - resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" - integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - "@types/node-fetch@^2.5.0", "@types/node-fetch@^2.5.4", "@types/node-fetch@^2.5.7": version "2.5.8" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" @@ -6575,12 +6571,12 @@ integrity sha512-+beqKQOh9PYxuHvijhVl+tIHvT6tuwOrE9m14zd+MT2A38KoKZhh7pYJ0SNleLtwDsiIxHDsIk9bv01oOxvSvA== "@types/serve-static@*": - version "1.13.3" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" - integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g== + version "1.13.9" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" + integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== dependencies: - "@types/express-serve-static-core" "*" - "@types/mime" "*" + "@types/mime" "^1" + "@types/node" "*" "@types/set-cookie-parser@^2.4.0": version "2.4.0" @@ -7101,7 +7097,7 @@ dependencies: tslib "^1.9.3" -"@xobotyi/scrollbar-width@1.9.5", "@xobotyi/scrollbar-width@^1.9.5": +"@xobotyi/scrollbar-width@^1.9.5": version "1.9.5" resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== @@ -7299,7 +7295,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.1, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5, ajv@^6.7.0: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.1, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5, ajv@^6.7.0, ajv@~6.12.6: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -7455,49 +7451,30 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -apollo-cache-control@^0.11.6: - version "0.11.6" - resolved "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.11.6.tgz#f7bdf924272af47ac474cf3f3f35cfc038cc9485" - integrity sha512-YZ+uuIG+fPy+mkpBS2qKF0v1qlzZ3PW6xZVaDukeK3ed3iAs4L/2YnkTqau3OmoF/VPzX2FmSkocX/OVd59YSw== +apollo-cache-control@^0.14.0: + version "0.14.0" + resolved "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.14.0.tgz#95f20c3e03e7994e0d1bd48c59aeaeb575ed0ce7" + integrity sha512-qN4BCq90egQrgNnTRMUHikLZZAprf3gbm8rC5Vwmc6ZdLolQ7bFsa769Hqi6Tq/lS31KLsXBLTOsRbfPHph12w== dependencies: - apollo-server-env "^3.0.0" - apollo-server-plugin-base "^0.10.4" + apollo-server-env "^3.1.0" + apollo-server-plugin-base "^0.13.0" -apollo-datasource@^0.7.3: - version "0.7.3" - resolved "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.7.3.tgz#c824eb1457bdee5a3173ced0e35e594547e687a0" - integrity sha512-PE0ucdZYjHjUyXrFWRwT02yLcx2DACsZ0jm1Mp/0m/I9nZu/fEkvJxfsryXB6JndpmQO77gQHixf/xGCN976kA== +apollo-datasource@^0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.9.0.tgz#b0b2913257a6103a5f4c03cb56d78a30e9d850db" + integrity sha512-y8H99NExU1Sk4TvcaUxTdzfq2SZo6uSj5dyh75XSQvbpH6gdAXIW9MaBcvlNC7n0cVPsidHmOcHOWxJ/pTXGjA== dependencies: - apollo-server-caching "^0.5.3" - apollo-server-env "^3.0.0" + apollo-server-caching "^0.7.0" + apollo-server-env "^3.1.0" -apollo-env@^0.6.5: - version "0.6.5" - resolved "https://registry.npmjs.org/apollo-env/-/apollo-env-0.6.5.tgz#5a36e699d39e2356381f7203493187260fded9f3" - integrity sha512-jeBUVsGymeTHYWp3me0R2CZRZrFeuSZeICZHCeRflHTfnQtlmbSXdy5E0pOyRM9CU4JfQkKDC98S1YglQj7Bzg== +apollo-graphql@^0.9.0: + version "0.9.3" + resolved "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.9.3.tgz#1ca6f625322ae10a66f57a39642849a07a7a5dc9" + integrity sha512-rcAl2E841Iko4kSzj4Pt3PRBitmyq1MvoEmpl04TQSpGnoVgl1E/ZXuLBYxMTSnEAm7umn2IsoY+c6Ll9U/10A== dependencies: - "@types/node-fetch" "2.5.7" - core-js "^3.0.1" - node-fetch "^2.2.0" - sha.js "^2.4.11" - -apollo-env@^0.6.6: - version "0.6.6" - resolved "https://registry.npmjs.org/apollo-env/-/apollo-env-0.6.6.tgz#d7880805c4e96ee3d4142900a405176a04779438" - integrity sha512-hXI9PjJtzmD34XviBU+4sPMOxnifYrHVmxpjykqI/dUD2G3yTiuRaiQqwRwB2RCdwC1Ug/jBfoQ/NHDTnnjndQ== - dependencies: - "@types/node-fetch" "2.5.7" - core-js "^3.0.1" - node-fetch "^2.2.0" - sha.js "^2.4.11" - -apollo-graphql@^0.6.0: - version "0.6.1" - resolved "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.6.1.tgz#d0bf0aff76f445de3da10e08f6974f1bf65f5753" - integrity sha512-ZRXAV+k+hboCVS+FW86FW/QgnDR7gm/xMUwJPGXEbV53OLGuQQdIT0NCYK7AzzVkCfsbb7NJ3mmEclkZY9uuxQ== - dependencies: - apollo-env "^0.6.6" + core-js-pure "^3.10.2" lodash.sortby "^4.7.0" + sha.js "^2.4.11" apollo-link-http-common@^0.2.14: version "0.2.16" @@ -7518,12 +7495,12 @@ apollo-link@^1.2.12, apollo-link@^1.2.14: tslib "^1.9.3" zen-observable-ts "^0.8.21" -apollo-reporting-protobuf@^0.6.2: - version "0.6.2" - resolved "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.6.2.tgz#5572866be9b77f133916532b10e15fbaa4158304" - integrity sha512-WJTJxLM+MRHNUxt1RTl4zD0HrLdH44F2mDzMweBj1yHL0kSt8I1WwoiF/wiGVSpnG48LZrBegCaOJeuVbJTbtw== +apollo-reporting-protobuf@^0.8.0: + version "0.8.0" + resolved "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.8.0.tgz#ae9d967934d3d8ed816fc85a0d8068ef45c371b9" + integrity sha512-B3XmnkH6Y458iV6OsA7AhfwvTgeZnFq9nPVjbxmLKnvfkEl8hYADtz724uPa0WeBiD7DSFcnLtqg9yGmCkBohg== dependencies: - "@apollo/protobufjs" "^1.0.3" + "@apollo/protobufjs" "1.2.2" apollo-server-caching@0.5.1: version "0.5.1" @@ -7532,95 +7509,95 @@ apollo-server-caching@0.5.1: dependencies: lru-cache "^5.0.0" -apollo-server-caching@^0.5.3: - version "0.5.3" - resolved "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.3.tgz#cf42a77ad09a46290a246810075eaa029b5305e1" - integrity sha512-iMi3087iphDAI0U2iSBE9qtx9kQoMMEWr6w+LwXruBD95ek9DWyj7OeC2U/ngLjRsXM43DoBDXlu7R+uMjahrQ== +apollo-server-caching@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.7.0.tgz#e6d1e68e3bb571cba63a61f60b434fb771c6ff39" + integrity sha512-MsVCuf/2FxuTFVhGLK13B+TZH9tBd2qkyoXKKILIiGcZ5CDUEBO14vIV63aNkMkS1xxvK2U4wBcuuNj/VH2Mkw== dependencies: lru-cache "^6.0.0" -apollo-server-core@^2.21.0: - version "2.21.0" - resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.21.0.tgz#12ee11aee61fa124f11b1d73cae2e068112a3a53" - integrity sha512-GtIiq2F0dVDLzzIuO5+dK/pGq/sGxYlKCqAuQQqzYg0fvZ7fukyluXtcTe0tMI+FJZjU0j0WnKgiLsboCoAlPQ== +apollo-server-core@^2.21.0, apollo-server-core@^2.25.2: + version "2.25.2" + resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.25.2.tgz#ff65da5e512d9b5ca54c8e5e8c78ee28b5987247" + integrity sha512-lrohEjde2TmmDTO7FlOs8x5QQbAS0Sd3/t0TaK2TWaodfzi92QAvIsq321Mol6p6oEqmjm8POIDHW1EuJd7XMA== dependencies: - "@apollographql/apollo-tools" "^0.4.3" - "@apollographql/graphql-playground-html" "1.6.26" + "@apollographql/apollo-tools" "^0.5.0" + "@apollographql/graphql-playground-html" "1.6.27" "@apollographql/graphql-upload-8-fork" "^8.1.3" + "@josephg/resolvable" "^1.0.0" "@types/ws" "^7.0.0" - apollo-cache-control "^0.11.6" - apollo-datasource "^0.7.3" - apollo-graphql "^0.6.0" - apollo-reporting-protobuf "^0.6.2" - apollo-server-caching "^0.5.3" - apollo-server-env "^3.0.0" - apollo-server-errors "^2.4.2" - apollo-server-plugin-base "^0.10.4" - apollo-server-types "^0.6.3" - apollo-tracing "^0.12.2" + apollo-cache-control "^0.14.0" + apollo-datasource "^0.9.0" + apollo-graphql "^0.9.0" + apollo-reporting-protobuf "^0.8.0" + apollo-server-caching "^0.7.0" + apollo-server-env "^3.1.0" + apollo-server-errors "^2.5.0" + apollo-server-plugin-base "^0.13.0" + apollo-server-types "^0.9.0" + apollo-tracing "^0.15.0" async-retry "^1.2.1" fast-json-stable-stringify "^2.0.0" - graphql-extensions "^0.12.8" + graphql-extensions "^0.15.0" graphql-tag "^2.11.0" graphql-tools "^4.0.8" loglevel "^1.6.7" lru-cache "^6.0.0" sha.js "^2.4.11" - subscriptions-transport-ws "^0.9.11" + subscriptions-transport-ws "^0.9.19" uuid "^8.0.0" - ws "^6.0.0" -apollo-server-env@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-3.0.0.tgz#0157c51f52b63aee39af190760acf789ffc744d9" - integrity sha512-tPSN+VttnPsoQAl/SBVUpGbLA97MXG990XIwq6YUnJyAixrrsjW1xYG7RlaOqetxm80y5mBZKLrRDiiSsW/vog== +apollo-server-env@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-3.1.0.tgz#0733c2ef50aea596cc90cf40a53f6ea2ad402cd0" + integrity sha512-iGdZgEOAuVop3vb0F2J3+kaBVi4caMoxefHosxmgzAbbSpvWehB8Y1QiSyyMeouYC38XNVk5wnZl+jdGSsWsIQ== dependencies: - node-fetch "^2.1.2" + node-fetch "^2.6.1" util.promisify "^1.0.0" -apollo-server-errors@^2.4.2: - version "2.4.2" - resolved "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.2.tgz#1128738a1d14da989f58420896d70524784eabe5" - integrity sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ== +apollo-server-errors@^2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.5.0.tgz#5d1024117c7496a2979e3e34908b5685fe112b68" + integrity sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA== apollo-server-express@^2.16.1, apollo-server-express@^2.21.0: - version "2.21.0" - resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.21.0.tgz#29bd4ec728e1992da240c5956c3ce6d95c1d252e" - integrity sha512-zbOSNGuxUjlOFZnRrbMpga3pKDEroitF4NAqoVxgBivx7v2hGsE7rljct3PucTx2cMN90AyYe3cU4oA8jBxZIQ== + version "2.25.2" + resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.25.2.tgz#58cd819694ff4c2dec6945a95c5dff6aa2719ef6" + integrity sha512-A2gF2e85vvDugPlajbhr0A14cDFDIGX0mteNOJ8P3Z3cIM0D4hwrWxJidI+SzobefDIyIHu1dynFedJVhV0euQ== dependencies: - "@apollographql/graphql-playground-html" "1.6.26" + "@apollographql/graphql-playground-html" "1.6.27" "@types/accepts" "^1.3.5" "@types/body-parser" "1.19.0" - "@types/cors" "2.8.8" - "@types/express" "4.17.7" - "@types/express-serve-static-core" "4.17.18" + "@types/cors" "2.8.10" + "@types/express" "^4.17.12" + "@types/express-serve-static-core" "^4.17.21" accepts "^1.3.5" - apollo-server-core "^2.21.0" - apollo-server-types "^0.6.3" + apollo-server-core "^2.25.2" + apollo-server-types "^0.9.0" body-parser "^1.18.3" - cors "^2.8.4" + cors "^2.8.5" express "^4.17.1" graphql-subscriptions "^1.0.0" graphql-tools "^4.0.8" parseurl "^1.3.2" - subscriptions-transport-ws "^0.9.16" + subscriptions-transport-ws "^0.9.19" type-is "^1.6.16" -apollo-server-plugin-base@^0.10.4: - version "0.10.4" - resolved "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.10.4.tgz#fbf73f64f95537ca9f9639dd7c535eb5eeb95dcd" - integrity sha512-HRhbyHgHFTLP0ImubQObYhSgpmVH4Rk1BinnceZmwudIVLKrqayIVOELdyext/QnSmmzg5W7vF3NLGBcVGMqDg== +apollo-server-plugin-base@^0.13.0: + version "0.13.0" + resolved "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.13.0.tgz#3f85751a420d3c4625355b6cb3fbdd2acbe71f13" + integrity sha512-L3TMmq2YE6BU6I4Tmgygmd0W55L+6XfD9137k+cWEBFu50vRY4Re+d+fL5WuPkk5xSPKd/PIaqzidu5V/zz8Kg== dependencies: - apollo-server-types "^0.6.3" + apollo-server-types "^0.9.0" -apollo-server-types@^0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.6.3.tgz#f7aa25ff7157863264d01a77d7934aa6e13399e8" - integrity sha512-aVR7SlSGGY41E1f11YYz5bvwA89uGmkVUtzMiklDhZ7IgRJhysT5Dflt5IuwDxp+NdQkIhVCErUXakopocFLAg== +apollo-server-types@^0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.9.0.tgz#ccf550b33b07c48c72f104fbe2876232b404848b" + integrity sha512-qk9tg4Imwpk732JJHBkhW0jzfG0nFsLqK2DY6UhvJf7jLnRePYsPxWfPiNkxni27pLE2tiNlCwoDFSeWqpZyBg== dependencies: - apollo-reporting-protobuf "^0.6.2" - apollo-server-caching "^0.5.3" - apollo-server-env "^3.0.0" + apollo-reporting-protobuf "^0.8.0" + apollo-server-caching "^0.7.0" + apollo-server-env "^3.1.0" apollo-server@^2.16.1: version "2.21.0" @@ -7634,13 +7611,13 @@ apollo-server@^2.16.1: graphql-tools "^4.0.8" stoppable "^1.1.0" -apollo-tracing@^0.12.2: - version "0.12.2" - resolved "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.12.2.tgz#a261c3970bb421b6dadf50cd85d75b2567a7e52c" - integrity sha512-SYN4o0C0wR1fyS3+P0FthyvsQVHFopdmN3IU64IaspR/RZScPxZ3Ae8uu++fTvkQflAkglnFM0aX6DkZERBp6w== +apollo-tracing@^0.15.0: + version "0.15.0" + resolved "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.15.0.tgz#237fbbbf669aee4370b7e9081b685eabaa8ce84a" + integrity sha512-UP0fztFvaZPHDhIB/J+qGuy6hWO4If069MGC98qVs0I8FICIGu4/8ykpX3X3K6RtaQ56EDAWKykCxFv4ScxMeA== dependencies: - apollo-server-env "^3.0.0" - apollo-server-plugin-base "^0.10.4" + apollo-server-env "^3.1.0" + apollo-server-plugin-base "^0.13.0" apollo-upload-client@^13.0.0: version "13.0.0" @@ -7860,7 +7837,7 @@ arrify@^2.0.0, arrify@^2.0.1: resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@^2.0.0, asap@~2.0.3, asap@~2.0.6: +asap@^2.0.0, asap@~2.0.3: version "2.0.6" resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -9201,12 +9178,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e" - integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== - -camelcase@^6.2.0: +camelcase@^6.0.0, camelcase@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== @@ -10236,12 +10208,17 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== -core-js@3, core-js@^3.0.1, core-js@^3.0.4, core-js@^3.5.0, core-js@^3.6.0, core-js@^3.6.5, core-js@^3.8.2: +core-js-pure@^3.10.2, core-js-pure@^3.6.5: + version "3.15.1" + resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.1.tgz#8356f6576fa2aa8e54ca6fe02620968ff010eed7" + integrity sha512-OZuWHDlYcIda8sJLY4Ec6nWq2hRjlyCqCZ+jCflyleMkVt3tPedDVErvHslyS2nbO+SlBFMSBJYvtLMwxnrzjA== + +core-js@3, core-js@^3.0.1, core-js@^3.0.4, core-js@^3.6.0, core-js@^3.6.5, core-js@^3.8.2: version "3.15.0" resolved "https://registry.npmjs.org/core-js/-/core-js-3.15.0.tgz#db9554ebce0b6fd90dc9b1f2465c841d2d055044" integrity sha512-GUbtPllXMYRzIgHNZ4dTYTcUemls2cni83Q4Q/TrFONHfhcg9oEGOtaGHfb0cpzec60P96UKPvMkjX1jET8rUw== -core-js@^2.4.0, core-js@^2.5.7, core-js@^2.6.10, core-js@^2.6.5: +core-js@^2.4.0, core-js@^2.6.10: version "2.6.11" resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== @@ -10251,7 +10228,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cors@^2.8.4, cors@^2.8.5: +cors@^2.8.5: version "2.8.5" resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== @@ -10405,7 +10382,7 @@ cross-fetch@3.0.6: dependencies: node-fetch "2.6.1" -cross-fetch@3.1.4, cross-fetch@^3.0.4, cross-fetch@^3.0.6: +cross-fetch@3.1.4, cross-fetch@^3.0.4, cross-fetch@^3.0.6, cross-fetch@^3.1.3: version "3.1.4" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== @@ -10540,6 +10517,22 @@ css-loader@^3.5.3: schema-utils "^2.7.0" semver "^6.3.0" +css-loader@^5.2.6: + version "5.2.6" + resolved "https://registry.npmjs.org/css-loader/-/css-loader-5.2.6.tgz#c3c82ab77fea1f360e587d871a6811f4450cc8d1" + integrity sha512-0wyN5vXMQZu6BvjbrPdUJvkCzGEO24HC7IS7nW4llc6BBFC+zwR9CKtYGv63Puzsg10L/o12inMY5/2ByzfD6w== + dependencies: + icss-utils "^5.1.0" + loader-utils "^2.0.0" + postcss "^8.2.15" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^3.0.0" + semver "^7.3.5" + css-select-base-adapter@^0.1.1: version "0.1.1" resolved "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" @@ -14245,14 +14238,14 @@ graphql-config@^3.0.2, graphql-config@^3.3.0: minimatch "3.0.4" string-env-interpolation "1.0.1" -graphql-extensions@^0.12.8: - version "0.12.8" - resolved "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.8.tgz#9cdc2c43d8fe5e0f6c3177a004ac011da2a8aa0f" - integrity sha512-xjsSaB6yKt9jarFNNdivl2VOx52WySYhxPgf8Y16g6GKZyAzBoIFiwyGw5PJDlOSUa6cpmzn6o7z8fVMbSAbkg== +graphql-extensions@^0.15.0: + version "0.15.0" + resolved "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.15.0.tgz#3f291f9274876b0c289fa4061909a12678bd9817" + integrity sha512-bVddVO8YFJPwuACn+3pgmrEg6I8iBuYLuwvxiE+lcQQ7POotVZxm2rgGw0PvVYmWWf3DT7nTVDZ5ROh/ALp8mA== dependencies: - "@apollographql/apollo-tools" "^0.4.3" - apollo-server-env "^3.0.0" - apollo-server-types "^0.6.3" + "@apollographql/apollo-tools" "^0.5.0" + apollo-server-env "^3.1.0" + apollo-server-types "^0.9.0" graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2.8.2: version "2.8.2" @@ -14991,7 +14984,7 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: dependencies: postcss "^7.0.14" -icss-utils@^5.0.0: +icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== @@ -15482,10 +15475,10 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" -is-core-module@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" - integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== +is-core-module@^2.1.0, is-core-module@^2.2.0: + version "2.4.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== dependencies: has "^1.0.3" @@ -16920,9 +16913,9 @@ json5@^1.0.1: minimist "^1.2.0" json5@^2.1.1, json5@^2.1.2, json5@^2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + version "2.2.0" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== dependencies: minimist "^1.2.5" @@ -18971,7 +18964,7 @@ nan@2.14.1, nan@^2.12.1, nan@^2.14.0: resolved "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== -nano-css@^5.1.0, nano-css@^5.2.1, nano-css@^5.3.1: +nano-css@^5.1.0, nano-css@^5.3.1: version "5.3.1" resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.3.1.tgz#b709383e07ad3be61f64edffacb9d98250b87a1f" integrity sha512-ENPIyNzANQRyYVvb62ajDd7PAyIgS2LIUnT9ewih4yrXSZX4hKoUwssy8WjUH++kEOA5wUTMgNnV7ko5n34kUA== @@ -18985,15 +18978,10 @@ nano-css@^5.1.0, nano-css@^5.2.1, nano-css@^5.3.1: stacktrace-js "^2.0.2" stylis "^4.0.6" -nanoid@^2.1.0: - version "2.1.11" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" - integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== - -nanoid@^3.1.22: - version "3.1.22" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" - integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== +nanoid@^3.1.23: + version "3.1.23" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" + integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== nanomatch@^1.2.9: version "1.2.13" @@ -19124,7 +19112,7 @@ node-dir@^0.1.10, node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@2.6.1, node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -19802,9 +19790,9 @@ optionator@^0.9.1: word-wrap "^1.2.3" ora@^5.3.0: - version "5.4.0" - resolved "https://registry.npmjs.org/ora/-/ora-5.4.0.tgz#42eda4855835b9cd14d33864c97a3c95a3f56bf4" - integrity sha512-1StwyXQGoU6gdjYkyVcqOLnVlbKj+6yPNNOxJVgpt9t4eksKjiriiHuxktLYkgllwk+D6MbC4ihH84L1udRXPg== + version "5.4.1" + resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== dependencies: bl "^4.1.0" chalk "^4.1.0" @@ -21178,14 +21166,14 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^ source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.0.2, postcss@^8.1.0: - version "8.2.13" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.2.13.tgz#dbe043e26e3c068e45113b1ed6375d2d37e2129f" - integrity sha512-FCE5xLH+hjbzRdpbRb1IMCvPv9yZx2QnDarBEYSN0N0HYk+TcXsEhwdFcFb+SRWOKzKGErhIEbBK2ogyLdTtfQ== +postcss@^8.0.2, postcss@^8.1.0, postcss@^8.2.15: + version "8.3.5" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709" + integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== dependencies: colorette "^1.2.2" - nanoid "^3.1.22" - source-map "^0.6.1" + nanoid "^3.1.23" + source-map-js "^0.6.2" postgres-array@~1.0.0: version "1.0.3" @@ -21395,13 +21383,6 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -promise@^8.0.3: - version "8.1.0" - resolved "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" - integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== - dependencies: - asap "~2.0.6" - prompts@2.4.0, prompts@^2.0.1: version "2.4.0" resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" @@ -21767,18 +21748,6 @@ rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-app-polyfill@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz#890f8d7f2842ce6073f030b117de9130a5f385f0" - integrity sha512-OfBnObtnGgLGfweORmdZbyEz+3dgVePQBb3zipiaDsMHV1NpWm0rDFYIVXFV/AK+x4VIIfWHhrdMIeoTLyRr2g== - dependencies: - core-js "^3.5.0" - object-assign "^4.1.1" - promise "^8.0.3" - raf "^3.4.1" - regenerator-runtime "^0.13.3" - whatwg-fetch "^3.0.0" - react-beautiful-dnd@^13.0.0: version "13.0.0" resolved "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40" @@ -22300,26 +22269,6 @@ react-use@^12.2.0: ts-easing "^0.2.0" tslib "^1.10.0" -react-use@^15.3.3: - version "15.3.8" - resolved "https://registry.npmjs.org/react-use/-/react-use-15.3.8.tgz#ca839ac7fb3d696e5ccbeabbc8dadc2698969d30" - integrity sha512-GeGcrmGuUvZrY5wER3Lnph9DSYhZt5nEjped4eKDq8BRGr2CnLf9bDQWG9RFc7oCPphnscUUdOovzq0E5F2c6Q== - dependencies: - "@types/js-cookie" "2.2.6" - "@xobotyi/scrollbar-width" "1.9.5" - copy-to-clipboard "^3.2.0" - fast-deep-equal "^3.1.3" - fast-shallow-equal "^1.0.0" - js-cookie "^2.2.1" - nano-css "^5.2.1" - react-universal-interface "^0.6.2" - resize-observer-polyfill "^1.5.1" - screenfull "^5.0.0" - set-harmonic-interval "^1.0.1" - throttle-debounce "^2.1.0" - ts-easing "^0.2.0" - tslib "^2.0.0" - react-use@^17.2.4: version "17.2.4" resolved "https://registry.npmjs.org/react-use/-/react-use-17.2.4.tgz#1f89be3db0a8237c79253db0a15e12bbe3cfeff1" @@ -22702,7 +22651,7 @@ regenerator-runtime@^0.11.0: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: version "0.13.7" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== @@ -23037,6 +22986,14 @@ resolve@~1.17.0: dependencies: path-parse "^1.0.6" +resolve@~1.19.0: + version "1.19.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -23677,13 +23634,6 @@ shellwords@^0.1.1: resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -shortid@^2.2.14: - version "2.2.15" - resolved "https://registry.npmjs.org/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122" - integrity sha512-5EaCy2mx2Jgc/Fdn9uuDuNIIfWBpzY4XIlhoqtXF6qsf+/+SGZ+FxDdX/ZsMZiWupIWNqAEmiNY4RC+LSmCeOw== - dependencies: - nanoid "^2.1.0" - shx@^0.3.2: version "0.3.3" resolved "https://registry.npmjs.org/shx/-/shx-0.3.3.tgz#681a88c7c10db15abe18525349ed474f0f1e7b9f" @@ -23916,6 +23866,11 @@ source-list-map@^2.0.0: resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== +source-map-js@^0.6.2: + version "0.6.2" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" + integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -24658,16 +24613,16 @@ stylis@^4.0.6: resolved "https://registry.npmjs.org/stylis/-/stylis-4.0.7.tgz#412a90c28079417f3d27c028035095e4232d2904" integrity sha512-OFFeUXFgwnGOKvEXaSv0D0KQ5ADP0n6g3SVONx6I/85JzNZ3u50FRwB3lVIk1QO2HNdI75tbVzc4Z66Gdp9voA== -subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.16, subscriptions-transport-ws@^0.9.18: - version "0.9.18" - resolved "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz#bcf02320c911fbadb054f7f928e51c6041a37b97" - integrity sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA== +subscriptions-transport-ws@^0.9.18, subscriptions-transport-ws@^0.9.19: + version "0.9.19" + resolved "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz#10ca32f7e291d5ee8eb728b9c02e43c52606cdcf" + integrity sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw== dependencies: backo2 "^1.0.2" eventemitter3 "^3.1.0" iterall "^1.2.1" symbol-observable "^1.0.4" - ws "^5.2.0" + ws "^5.2.0 || ^6.0.0 || ^7.0.0" sucrase@^3.18.0, sucrase@^3.18.2: version "3.18.2" @@ -25532,6 +25487,22 @@ ts-log@^2.2.3: resolved "https://registry.npmjs.org/ts-log/-/ts-log-2.2.3.tgz#4da5640fe25a9fb52642cd32391c886721318efb" integrity sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w== +ts-node@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.0.0.tgz#05f10b9a716b0b624129ad44f0ea05dac84ba3be" + integrity sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg== + dependencies: + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + ts-node@^9, ts-node@^9.1.1: version "9.1.1" resolved "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" @@ -26138,10 +26109,10 @@ use-composed-ref@^1.0.0: dependencies: ts-essentials "^2.0.3" -use-immer@^0.5.1: - version "0.5.1" - resolved "https://registry.npmjs.org/use-immer/-/use-immer-0.5.1.tgz#3862ebbb194ebfe66b8eb221082c2fb2585f8fe1" - integrity sha512-Orb7PokM+jiLQfA1oJ3B3P7Guq0c2IxUHHmBpLeEMnJiz4ZOMlj9mkqq6D7iXJsnurRX0EOB134annf3RjEWHw== +use-immer@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/use-immer/-/use-immer-0.6.0.tgz#ca6aa5ade93018e2c65cf128d19ada54fc23f70d" + integrity sha512-dFGRfvWCqPDTOt/S431ETYTg6+uxbpb7A1pptufwXVzGJY3RlXr38+3wyLNpc6SbbmAKjWl6+EP6uW74fkEsXQ== use-isomorphic-layout-effect@^1.0.0: version "1.0.0" @@ -26619,7 +26590,7 @@ whatwg-fetch@^2.0.4: resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== -whatwg-fetch@^3.0.0, whatwg-fetch@^3.4.1: +whatwg-fetch@^3.4.1: version "3.4.1" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz#e5f871572d6879663fa5674c8f833f15a8425ab3" integrity sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ== @@ -26905,7 +26876,12 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^6.0.0, ws@^6.1.2, ws@^6.2.1: +"ws@^5.2.0 || ^6.0.0 || ^7.0.0": + version "7.5.0" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691" + integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== + +ws@^6.1.2, ws@^6.2.1: version "6.2.1" resolved "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== @@ -27023,10 +26999,10 @@ xpath@0.0.27: resolved "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== -xss@^1.0.6: - version "1.0.7" - resolved "https://registry.npmjs.org/xss/-/xss-1.0.7.tgz#a554cbd5e909324bd6893fb47fff441ad54e2a95" - integrity sha512-A9v7tblGvxu8TWXQC9rlpW96a+LN1lyw6wyhpTmmGW+FwRMactchBR3ROKSi33UPCUcUHSu8s9YP6F+K3Mw//w== +xss@^1.0.8: + version "1.0.9" + resolved "https://registry.npmjs.org/xss/-/xss-1.0.9.tgz#3ffd565571ff60d2e40db7f3b80b4677bec770d2" + integrity sha512-2t7FahYnGJys6DpHLhajusId7R0Pm2yTmuL0GV9+mV0ZlaLSnb2toBmppATfg5sWIhZQGlsTLoecSzya+l4EAQ== dependencies: commander "^2.20.3" cssfilter "0.0.10"