diff --git a/.changeset/breezy-ears-rule.md b/.changeset/breezy-ears-rule.md new file mode 100644 index 0000000000..753554187d --- /dev/null +++ b/.changeset/breezy-ears-rule.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +The config now consistently uses the term 'instance' to refer to a single GitLab API host. diff --git a/.changeset/bright-plums-cough.md b/.changeset/bright-plums-cough.md new file mode 100644 index 0000000000..9af30f002e --- /dev/null +++ b/.changeset/bright-plums-cough.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +**BREAKING**: Split out the hook for both lifecycle services so that the first parameter of `addShutdownHook` is the hook function, and the second is the options. diff --git a/.changeset/bright-teachers-compete.md b/.changeset/bright-teachers-compete.md new file mode 100644 index 0000000000..3cc900d613 --- /dev/null +++ b/.changeset/bright-teachers-compete.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +**BREAKING**: The plugin ID option passed to `createBackendPlugin` is now `pluginId`, rather than just `id`. This is to make it match `createBackendModule` more closely. diff --git a/.changeset/chilled-sheep-collect.md b/.changeset/chilled-sheep-collect.md new file mode 100644 index 0000000000..b944685e16 --- /dev/null +++ b/.changeset/chilled-sheep-collect.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Removed unnecessary `@backstage/cli` dependency. diff --git a/.changeset/few-moons-accept.md b/.changeset/few-moons-accept.md new file mode 100644 index 0000000000..c790e837c8 --- /dev/null +++ b/.changeset/few-moons-accept.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Replaced several monorepo lint rules with new rules from `@backstage/eslint-plugin`. See the [README](https://github.com/import-js/eslint-plugin-import/blob/main/packages/eslint-plugin/README.md) for a full list of rules. diff --git a/.changeset/four-tables-fix.md b/.changeset/four-tables-fix.md new file mode 100644 index 0000000000..f17f57e253 --- /dev/null +++ b/.changeset/four-tables-fix.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Added explicit return type signature for `mockServices.config()`. diff --git a/.changeset/great-goats-talk.md b/.changeset/great-goats-talk.md new file mode 100644 index 0000000000..92a1cd59ee --- /dev/null +++ b/.changeset/great-goats-talk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': minor +--- + +Adds a new tRPC API definition widget which users can utilize to integrate their tRPC API definitions into Backstage. diff --git a/.changeset/green-adults-retire.md b/.changeset/green-adults-retire.md new file mode 100644 index 0000000000..c13e8d664f --- /dev/null +++ b/.changeset/green-adults-retire.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dev-utils': minor +--- + +Introduced a new package for backend development utilities. Similar to how `@backstage/dev-utils` is used in the frontend. diff --git a/.changeset/green-guests-flow.md b/.changeset/green-guests-flow.md new file mode 100644 index 0000000000..688ff17dd9 --- /dev/null +++ b/.changeset/green-guests-flow.md @@ -0,0 +1,8 @@ +--- +'@backstage/backend-test-utils': patch +'@backstage/backend-app-api': patch +'@backstage/backend-common': patch +'@backstage/plugin-catalog-backend': patch +--- + +Updated usage of the lifecycle service. diff --git a/.changeset/grumpy-fireants-drop.md b/.changeset/grumpy-fireants-drop.md new file mode 100644 index 0000000000..92b08663f4 --- /dev/null +++ b/.changeset/grumpy-fireants-drop.md @@ -0,0 +1,5 @@ +--- +'@backstage/codemods': patch +--- + +Moved `commander` to being a regular dependency. diff --git a/.changeset/hip-chairs-double.md b/.changeset/hip-chairs-double.md new file mode 100644 index 0000000000..e2b03f2e66 --- /dev/null +++ b/.changeset/hip-chairs-double.md @@ -0,0 +1,19 @@ +--- +'@backstage/plugin-tech-radar': patch +--- + +Deprecate `RadarEntry.url` - use `RadarEntry.links` instead + +```diff +- url: 'https://www.javascript.com/', + key: 'javascript', + id: 'javascript', + title: 'JavaScript', + quadrant: 'languages', + links: [ ++ { ++ url: 'https://www.javascript.com/', ++ title: 'Learn more', ++ }, + ], +``` diff --git a/.changeset/hip-onions-tan.md b/.changeset/hip-onions-tan.md new file mode 100644 index 0000000000..01be0d4867 --- /dev/null +++ b/.changeset/hip-onions-tan.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': patch +--- + +Change the router's response to include the error message instead of its object type in case it fails during a search query because the messages have more info. diff --git a/.changeset/itchy-chairs-punch.md b/.changeset/itchy-chairs-punch.md new file mode 100644 index 0000000000..034a9bbdae --- /dev/null +++ b/.changeset/itchy-chairs-punch.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +Aligned opaque type markers to all use a `$$type` property with namespacing. diff --git a/.changeset/little-jars-lay.md b/.changeset/little-jars-lay.md new file mode 100644 index 0000000000..f8851f7adc --- /dev/null +++ b/.changeset/little-jars-lay.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-user-settings-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-periskop-backend': patch +'@backstage/backend-common': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-bazaar-backend': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-kafka-backend': patch +'@backstage/plugin-proxy-backend': patch +'@backstage/plugin-app-backend': patch +--- + +Updated usage of `createBackendPlugin`. diff --git a/.changeset/long-chefs-burn.md b/.changeset/long-chefs-burn.md new file mode 100644 index 0000000000..03a7b76db9 --- /dev/null +++ b/.changeset/long-chefs-burn.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Updated database factory to pass service deps required for restoring database state during development. diff --git a/.changeset/many-pans-unite.md b/.changeset/many-pans-unite.md new file mode 100644 index 0000000000..59c068cec1 --- /dev/null +++ b/.changeset/many-pans-unite.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-model': minor +--- + +Adds the tRPC API type to the catalog. diff --git a/.changeset/nine-ravens-kiss.md b/.changeset/nine-ravens-kiss.md new file mode 100644 index 0000000000..a507db6b03 --- /dev/null +++ b/.changeset/nine-ravens-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-adr': minor +--- + +The `AdrSearchResultListItem` component is now a search result extension. This means that when rendered as a child of components that render search extensions, the `result`, `rank`, and `highlight` properties are optional. See the [documentation](https://backstage.io/docs/features/search/how-to-guides#how-to-render-search-results-using-extensions) for more details. diff --git a/.changeset/old-ways-know.md b/.changeset/old-ways-know.md new file mode 100644 index 0000000000..60a1a923e8 --- /dev/null +++ b/.changeset/old-ways-know.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +The `DatabaseManager.forPlugin` method now accepts additional service dependencies. There is no need to update existing code to pass these dependencies. diff --git a/.changeset/proud-glasses-live.md b/.changeset/proud-glasses-live.md new file mode 100644 index 0000000000..ded7a53ce7 --- /dev/null +++ b/.changeset/proud-glasses-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-adr': patch +--- + +Support displaying an icon on `AdrSearchResultListItem` diff --git a/.changeset/rare-berries-give.md b/.changeset/rare-berries-give.md new file mode 100644 index 0000000000..39e360bf48 --- /dev/null +++ b/.changeset/rare-berries-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Packages without a declared `backstage.role` are now ignored. diff --git a/.changeset/red-kiwis-compare.md b/.changeset/red-kiwis-compare.md new file mode 100644 index 0000000000..6045367105 --- /dev/null +++ b/.changeset/red-kiwis-compare.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +The shutdown signal handlers are now installed as part of the backend instance rather than the lifecycle service, and explicitly cause the process to exit. diff --git a/.changeset/renovate-53e8b07.md b/.changeset/renovate-53e8b07.md new file mode 100644 index 0000000000..cb19d8fe8b --- /dev/null +++ b/.changeset/renovate-53e8b07.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Updated dependency `@asyncapi/react-component` to `1.0.0-next.47`. diff --git a/.changeset/silly-buckets-joke.md b/.changeset/silly-buckets-joke.md new file mode 100644 index 0000000000..113272c965 --- /dev/null +++ b/.changeset/silly-buckets-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Fixed a bug reading the `user.select` field expected from the `app-config.yaml` configuration diff --git a/.changeset/slow-carrots-sniff.md b/.changeset/slow-carrots-sniff.md new file mode 100644 index 0000000000..d32c2a281e --- /dev/null +++ b/.changeset/slow-carrots-sniff.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': minor +--- + +Improve performance when loading users via group membership. +Users data is now loaded from a paged query, rather than having to make an extra call per user to load each user's profiles. + +Note, there are still additional per user calls made to load user avatars diff --git a/.changeset/slow-moles-sin.md b/.changeset/slow-moles-sin.md new file mode 100644 index 0000000000..02e1952312 --- /dev/null +++ b/.changeset/slow-moles-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-circleci': patch +--- + +Making workflow a link diff --git a/.changeset/tall-tables-hide.md b/.changeset/tall-tables-hide.md new file mode 100644 index 0000000000..3e8011aff2 --- /dev/null +++ b/.changeset/tall-tables-hide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +--- + +Updated README to include newer API options for incremental entity providers diff --git a/.changeset/thin-donkeys-attack.md b/.changeset/thin-donkeys-attack.md new file mode 100644 index 0000000000..2fd0aebb6a --- /dev/null +++ b/.changeset/thin-donkeys-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Removed the `ref` from all `mockServices`. diff --git a/.changeset/tough-jeans-camp.md b/.changeset/tough-jeans-camp.md new file mode 100644 index 0000000000..8c7a53190f --- /dev/null +++ b/.changeset/tough-jeans-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/eslint-plugin': minor +--- + +Added a new ESLint plugin with common rules for Backstage projects. See the [README](https://github.com/import-js/eslint-plugin-import/blob/main/packages/eslint-plugin/README.md) for more details. diff --git a/.changeset/weak-radios-sing.md b/.changeset/weak-radios-sing.md new file mode 100644 index 0000000000..dff287b565 --- /dev/null +++ b/.changeset/weak-radios-sing.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added an experimental mode for the `package start` command for backend packages. Enabled by setting `EXPERIMENTAL_BACKEND_START`. diff --git a/.changeset/wicked-beers-walk.md b/.changeset/wicked-beers-walk.md new file mode 100644 index 0000000000..160a171eb1 --- /dev/null +++ b/.changeset/wicked-beers-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Updated `packages/app/cypress/.eslintrc.json` to remove the unnecessary `import/no-extraneous-dependencies` rule. diff --git a/.changeset/young-points-drop.md b/.changeset/young-points-drop.md new file mode 100644 index 0000000000..4309d54c2c --- /dev/null +++ b/.changeset/young-points-drop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Increased default page size to 999 (from 100) to reduce the number of calls made to the Microsoft Graph API. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 82d868a408..bb6f8da19b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,78 +4,77 @@ # The last matching pattern takes precedence. # https://help.github.com/articles/about-codeowners/ -* @backstage/reviewers -yarn.lock @backstage/reviewers @backstage-service -*/yarn.lock @backstage/reviewers @backstage-service -/.changeset/cost-insights-* @backstage/reviewers @backstage/silver-lining -/.changeset @backstage/reviewers @backstage/techdocs-core -/cypress/src/integration/plugins/techdocs.spec.ts @backstage/reviewers @backstage/techdocs-core -/docs/assets/search @backstage/reviewers @backstage/techdocs-core -/docs/features/search @backstage/reviewers @backstage/techdocs-core -/docs/features/techdocs @backstage/reviewers @backstage/techdocs-core -/docs/plugins/integrating-search-into-plugins.md @backstage/reviewers @backstage/techdocs-core -/packages/techdocs-cli @backstage/reviewers @backstage/techdocs-core -/packages/techdocs-cli-embedded-app @backstage/reviewers @backstage/techdocs-core -/plugins/adr @backstage/reviewers @kuangp -/plugins/adr-* @backstage/reviewers @kuangp -/plugins/allure @backstage/reviewers @deepak-bhardwaj-ps -/plugins/apache-airflow @backstage/reviewers @cmpadden -/plugins/api-docs @backstage/reviewers @backstage/sda-se-reviewers -/plugins/azure-devops @backstage/reviewers @marleypowell @awanlin -/plugins/azure-devops-backend @backstage/reviewers @marleypowell @awanlin -/plugins/azure-devops-common @backstage/reviewers @marleypowell @awanlin -/plugins/bitbucket-cloud-common @backstage/reviewers @pjungermann -/plugins/bitrise @backstage/reviewers @backstage/sda-se-reviewers -/plugins/catalog @backstage/reviewers @backstage/catalog-core -/plugins/catalog-* @backstage/reviewers @backstage/catalog-core -/plugins/catalog-backend-module-aws @backstage/reviewers @backstage/catalog-core @pjungermann -/plugins/catalog-backend-module-bitbucket-cloud @backstage/reviewers @backstage/catalog-core @pjungermann -/plugins/catalog-backend-module-msgraph @backstage/reviewers @backstage/catalog-core @pjungermann -/plugins/catalog-graph @backstage/reviewers @backstage/catalog-core @backstage/sda-se-reviewers -/plugins/circleci @backstage/reviewers @adamdmharvey -/plugins/cloudbuild @backstage/reviewers @trivago/ebarrios -/plugins/code-coverage @backstage/reviewers @alde @nissayeva -/plugins/code-coverage-backend @backstage/reviewers @alde @nissayeva -/plugins/cost-insights @backstage/reviewers @backstage/silver-lining -/plugins/cost-insights-* @backstage/reviewers @backstage/silver-lining -/plugins/events-backend @backstage/reviewers @pjungermann -/plugins/events-backend-module-aws-sqs @backstage/reviewers @pjungermann -/plugins/events-backend-module-azure @backstage/reviewers @pjungermann -/plugins/events-backend-module-bitbucket-cloud @backstage/reviewers @pjungermann -/plugins/events-backend-module-gerrit @backstage/reviewers @pjungermann -/plugins/events-backend-module-github @backstage/reviewers @pjungermann -/plugins/events-backend-module-gitlab @backstage/reviewers @pjungermann -/plugins/events-backend-test-utils @backstage/reviewers @pjungermann -/plugins/events-node @backstage/reviewers @pjungermann -/plugins/explore @backstage/reviewers @backstage/sda-se-reviewers -/plugins/explore-react @backstage/reviewers @backstage/sda-se-reviewers -/plugins/fossa @backstage/reviewers @backstage/sda-se-reviewers -/plugins/gcalendar @backstage/reviewers @szubster @ptychu @kielosz @alexrybch -/plugins/git-release-manager @backstage/reviewers @erikengervall -/plugins/home @backstage/reviewers @backstage/techdocs-core -/plugins/ilert @backstage/reviewers @yacut -/plugins/jenkins @backstage/reviewers @timja -/plugins/jenkins-backend @backstage/reviewers @timja -/plugins/kafka @backstage/reviewers @nirga @andrewthauer -/plugins/kafka-backend @backstage/reviewers @nirga @andrewthauer -/plugins/kubernetes @backstage/reviewers @backstage/warpspeed -/plugins/kubernetes-* @backstage/reviewers @backstage/warpspeed -/plugins/newrelic-dashboard @backstage/reviewers @mufaddal7 -/plugins/playlist @backstage/reviewers @kuangp -/plugins/playlist-* @backstage/reviewers @kuangp -/plugins/rollbar @backstage/reviewers @andrewthauer -/plugins/rollbar-backend @backstage/reviewers @andrewthauer -/plugins/scaffolder-backend-module-rails @backstage/reviewers @angeliski -/plugins/scaffolder-backend-module-yeoman @backstage/reviewers @pawelmitka -/plugins/search @backstage/reviewers @backstage/techdocs-core -/plugins/search-* @backstage/reviewers @backstage/techdocs-core -/plugins/sonarqube @backstage/reviewers @backstage/sda-se-reviewers -/plugins/stack-overflow @backstage/reviewers @backstage/techdocs-core -/plugins/stack-overflow-backend @backstage/reviewers @backstage/techdocs-core -/plugins/techdocs @backstage/reviewers @backstage/techdocs-core -/plugins/techdocs-* @backstage/reviewers @backstage/techdocs-core -/plugins/user-settings-backend @backstage/reviewers @backstage/sda-se-reviewers -/tech-insights-backend @backstage/reviewers @xantier @iain-b -/tech-insights-backend-module-jsonfc @backstage/reviewers @xantier @iain-b -/tech-insights-tech-insights-common @backstage/reviewers @xantier @iain-b -/tech-insights-tech-insights-node @backstage/reviewers @xantier @iain-b +* @backstage/maintainers +yarn.lock @backstage/maintainers @backstage-service +*/yarn.lock @backstage/maintainers @backstage-service +/.changeset/*.md +/cypress/src/integration/plugins/techdocs.spec.ts @backstage/techdocs-core +/docs/assets/search @backstage/techdocs-core +/docs/features/search @backstage/techdocs-core +/docs/features/techdocs @backstage/techdocs-core +/docs/plugins/integrating-search-into-plugins.md @backstage/techdocs-core +/packages/techdocs-cli @backstage/techdocs-core +/packages/techdocs-cli-embedded-app @backstage/techdocs-core +/plugins/adr @backstage/maintainers @kuangp +/plugins/adr-* @backstage/maintainers @kuangp +/plugins/allure @backstage/maintainers @deepak-bhardwaj-ps +/plugins/apache-airflow @backstage/maintainers @cmpadden +/plugins/api-docs @backstage/maintainers @backstage/sda-se-reviewers +/plugins/azure-devops @backstage/maintainers @marleypowell @awanlin +/plugins/azure-devops-backend @backstage/maintainers @marleypowell @awanlin +/plugins/azure-devops-common @backstage/maintainers @marleypowell @awanlin +/plugins/bitbucket-cloud-common @backstage/maintainers @pjungermann +/plugins/bitrise @backstage/maintainers @backstage/sda-se-reviewers +/plugins/catalog @backstage/maintainers @backstage/catalog-core +/plugins/catalog-* @backstage/maintainers @backstage/catalog-core +/plugins/catalog-backend-module-aws @backstage/maintainers @backstage/catalog-core @pjungermann +/plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/catalog-core @pjungermann +/plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/catalog-core @pjungermann +/plugins/catalog-graph @backstage/maintainers @backstage/catalog-core @backstage/sda-se-reviewers +/plugins/circleci @backstage/maintainers @adamdmharvey +/plugins/cloudbuild @backstage/maintainers @trivago/ebarrios +/plugins/code-coverage @backstage/maintainers @alde @nissayeva +/plugins/code-coverage-backend @backstage/maintainers @alde @nissayeva +/plugins/cost-insights @backstage/maintainers @backstage/silver-lining +/plugins/cost-insights-* @backstage/maintainers @backstage/silver-lining +/plugins/events-backend @backstage/maintainers @pjungermann +/plugins/events-backend-module-aws-sqs @backstage/maintainers @pjungermann +/plugins/events-backend-module-azure @backstage/maintainers @pjungermann +/plugins/events-backend-module-bitbucket-cloud @backstage/maintainers @pjungermann +/plugins/events-backend-module-gerrit @backstage/maintainers @pjungermann +/plugins/events-backend-module-github @backstage/maintainers @pjungermann +/plugins/events-backend-module-gitlab @backstage/maintainers @pjungermann +/plugins/events-backend-test-utils @backstage/maintainers @pjungermann +/plugins/events-node @backstage/maintainers @pjungermann +/plugins/explore @backstage/maintainers @backstage/sda-se-reviewers +/plugins/explore-react @backstage/maintainers @backstage/sda-se-reviewers +/plugins/fossa @backstage/maintainers @backstage/sda-se-reviewers +/plugins/gcalendar @backstage/maintainers @szubster @ptychu @kielosz @alexrybch +/plugins/git-release-manager @backstage/maintainers @erikengervall +/plugins/home @backstage/techdocs-core +/plugins/ilert @backstage/maintainers @yacut +/plugins/jenkins @backstage/maintainers @timja +/plugins/jenkins-backend @backstage/maintainers @timja +/plugins/kafka @backstage/maintainers @nirga @andrewthauer +/plugins/kafka-backend @backstage/maintainers @nirga @andrewthauer +/plugins/kubernetes @backstage/maintainers @backstage/warpspeed +/plugins/kubernetes-* @backstage/maintainers @backstage/warpspeed +/plugins/newrelic-dashboard @backstage/maintainers @mufaddal7 +/plugins/playlist @backstage/maintainers @kuangp +/plugins/playlist-* @backstage/maintainers @kuangp +/plugins/rollbar @backstage/maintainers @andrewthauer +/plugins/rollbar-backend @backstage/maintainers @andrewthauer +/plugins/scaffolder-backend-module-rails @backstage/maintainers @angeliski +/plugins/scaffolder-backend-module-yeoman @backstage/maintainers @pawelmitka +/plugins/search @backstage/techdocs-core +/plugins/search-* @backstage/techdocs-core +/plugins/sonarqube @backstage/maintainers @backstage/sda-se-reviewers +/plugins/stack-overflow @backstage/techdocs-core +/plugins/stack-overflow-backend @backstage/techdocs-core +/plugins/techdocs @backstage/techdocs-core +/plugins/techdocs-* @backstage/techdocs-core +/plugins/user-settings-backend @backstage/maintainers @backstage/sda-se-reviewers +/tech-insights-backend @backstage/maintainers @xantier @iain-b +/tech-insights-backend-module-jsonfc @backstage/maintainers @xantier @iain-b +/tech-insights-tech-insights-common @backstage/maintainers @xantier @iain-b +/tech-insights-tech-insights-node @backstage/maintainers @xantier @iain-b diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index f24b701d36..e6380a17f0 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -9,6 +9,7 @@ Airbrake Airbrakes Alaria Alef +allowlisted Anddddd Apdex api @@ -23,6 +24,7 @@ Avro backend's backported backporting +BEPs Bigtable Billett bitbucket @@ -95,6 +97,7 @@ dockerfiles Dockerize dockerode Docusaurus +Dominik DOMPurify don'ts dynatrace @@ -119,6 +122,7 @@ Firekube Firestore Fiverr Francesco +Frontside gerrit Gerrit gitbeaker @@ -138,6 +142,7 @@ Hackathons haproxy hardcoded Helidon +Henneke Heroku hoc horizontalpodautoscalers @@ -167,12 +172,14 @@ jsonnet jsx JWTs Kaewkasi +Kaswell Keyv Knex KPIs +Kuang kubectl kubernetes -kubernetes +Lainfiesta ldap learnings Leasot @@ -336,6 +343,7 @@ subfolders subheader subheaders subkey +subpath subroutes subtree superfences @@ -412,10 +420,3 @@ zod Zolotusky zoomable zsh -Lainfiesta -allowlisted -Dominik -Henneke -Kuang -Frontside -Kaswell \ No newline at end of file diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index e18aa9d428..aaf0bb1294 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -28,13 +28,13 @@ jobs: ref: 'refs/pull/${{ github.event.pull_request.number }}/merge' - name: fetch base run: git fetch --depth 1 origin ${{ github.base_ref }} - - uses: backstage/actions/changeset-feedback@v0.5.13 + - uses: backstage/actions/changeset-feedback@v0.6.0 name: Generate feedback with: - diffRef: 'origin/master' + diff-ref: 'origin/master' marker: issue-number: ${{ github.event.pull_request.number }} - botUsername: backstage-goalie[bot] + bot-username: backstage-goalie[bot] app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10db591f4c..16573cf5b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -63,7 +63,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -181,7 +181,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index dc221c1a1e..2e6573ede8 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -8,7 +8,7 @@ jobs: cron: runs-on: ubuntu-latest steps: - - uses: backstage/actions/cron@v0.5.13 + - uses: backstage/actions/cron@v0.6.0 with: app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index d8470d4124..909a36199c 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -26,7 +26,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index 5f337ace3e..c9cd22640c 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -26,7 +26,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index c05b850883..fa43271a96 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -68,7 +68,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -145,7 +145,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml index d16d0b55d7..9f2e5827be 100644 --- a/.github/workflows/issue.yaml +++ b/.github/workflows/issue.yaml @@ -10,4 +10,4 @@ jobs: if: github.repository == 'backstage/backstage' steps: - name: Issue sync - uses: backstage/actions/issue-sync@v0.5.13 + uses: backstage/actions/issue-sync@v0.6.0 diff --git a/.github/workflows/pr-review-comment.yaml b/.github/workflows/pr-review-comment.yaml index 11b590809a..261ce56177 100644 --- a/.github/workflows/pr-review-comment.yaml +++ b/.github/workflows/pr-review-comment.yaml @@ -35,7 +35,7 @@ jobs: const prNumber = artifact.name.slice('pr_number-'.length) core.setOutput('pr-number', prNumber); - - uses: backstage/actions/re-review@v0.5.13 + - uses: backstage/actions/re-review@v0.6.0 with: app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index acbd11aece..1cde52bfbb 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -18,7 +18,7 @@ jobs: if: github.repository == 'backstage/backstage' && ( github.event.pull_request || github.event.issue.pull_request ) steps: - name: PR sync - uses: backstage/actions/pr-sync@v0.5.13 + uses: backstage/actions/pr-sync@v0.6.0 with: github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 19cc5f8045..b84fe5c2e6 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -20,7 +20,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 9dcc9f63ef..dfd114c66a 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -23,7 +23,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 5c8e179958..714b108898 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -27,7 +27,7 @@ jobs: registry-url: https://registry.npmjs.org/ - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: linux-v16 diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index f687e4f5a4..16ddb04086 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -52,7 +52,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_kubernetes.yml b/.github/workflows/verify_kubernetes.yml index 7fbcf9fa3f..6d8a7682b5 100644 --- a/.github/workflows/verify_kubernetes.yml +++ b/.github/workflows/verify_kubernetes.yml @@ -28,7 +28,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 0e63393af8..38b685c539 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -35,7 +35,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.13 + uses: backstage/actions/yarn-install@v0.6.0 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} - name: storybook yarn install diff --git a/cypress/.eslintrc.js b/cypress/.eslintrc.js index a77de4a629..07f264452d 100644 --- a/cypress/.eslintrc.js +++ b/cypress/.eslintrc.js @@ -2,15 +2,6 @@ module.exports = { extends: [require.resolve('@backstage/cli/config/eslint.backend')], rules: { 'no-console': 0, - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: true, - optionalDependencies: false, - peerDependencies: false, - bundledDependencies: false, - }, - ], 'jest/valid-expect': 'off', 'jest/expect-expect': 'off', 'no-restricted-syntax': 'off', diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 0000000000..fd4db77d61 --- /dev/null +++ b/cypress/support/commands.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 0000000000..c71790471e --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/docs/assets/auth/gcp-iap-jwt-audience-code-popup.png b/docs/assets/auth/gcp-iap-jwt-audience-code-popup.png new file mode 100644 index 0000000000..72b69b3b01 Binary files /dev/null and b/docs/assets/auth/gcp-iap-jwt-audience-code-popup.png differ diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index f9a78bbc71..36eece85c5 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -29,7 +29,9 @@ auth: jwtHeader: x-custom-header # Optional: Only if you are using a custom header for the IAP JWT ``` -You can find the project number and service ID in the Google Cloud Console. +The full `audience` value can be obtained by visiting your [Identity-Aware Proxy Google Cloud console](https://console.cloud.google.com/security/iap), selecting your project, finding your Backend Service to proxy, clicking the 3 vertical dots then "Get JWT Audience Code", and copying from the resulting popup, which will look similar to the following: + +![Identity-Aware Proxy JWT Audience Code popup](../../assets/auth/gcp-iap-jwt-audience-code-popup.png) This config section must be in place for the provider to load at all. Now let's add the provider itself. diff --git a/docs/backend-system/architecture/04-plugins.md b/docs/backend-system/architecture/04-plugins.md index 7721b1c9f7..e73f086c43 100644 --- a/docs/backend-system/architecture/04-plugins.md +++ b/docs/backend-system/architecture/04-plugins.md @@ -22,7 +22,7 @@ import { } from '@backstage/backend-plugin-api'; export const examplePlugin = createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -53,7 +53,7 @@ export interface ExamplePluginOptions { export const examplePlugin = createBackendPlugin( (options?: ExamplePluginOptions) => ({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { diff --git a/docs/backend-system/architecture/05-extension-points.md b/docs/backend-system/architecture/05-extension-points.md index d16a773c0c..278c565472 100644 --- a/docs/backend-system/architecture/05-extension-points.md +++ b/docs/backend-system/architecture/05-extension-points.md @@ -44,7 +44,7 @@ class ActionsExtension implements ScaffolderActionsExtensionPoint { export const scaffolderPlugin = createBackendPlugin( { - id: 'scaffolder', + pluginId: 'scaffolder', register(env) { const actionsExtensions = new ActionsExtension(); env.registerExtensionPoint( diff --git a/docs/backend-system/architecture/07-naming-patterns.md b/docs/backend-system/architecture/07-naming-patterns.md index dc781a61ac..b4b4c6c70f 100644 --- a/docs/backend-system/architecture/07-naming-patterns.md +++ b/docs/backend-system/architecture/07-naming-patterns.md @@ -21,7 +21,7 @@ Example: ```ts export const catalogPlugin = createBackendPlugin({ - id: 'catalog', + pluginId: 'catalog', ... }) ``` @@ -36,7 +36,7 @@ export const catalogPlugin = createBackendPlugin({ Example: ```ts -export const catalogModuleGithubEntityProvider = createBackendPlugin({ +export const catalogModuleGithubEntityProvider = createBackendModule({ pluginId: 'catalog', moduleId: 'githubEntityProvider', ... diff --git a/docs/backend-system/building-plugins-and-modules/01-index.md b/docs/backend-system/building-plugins-and-modules/01-index.md index 5fefb3ff16..cae5052d04 100644 --- a/docs/backend-system/building-plugins-and-modules/01-index.md +++ b/docs/backend-system/building-plugins-and-modules/01-index.md @@ -36,7 +36,7 @@ import { import { createExampleRouter } from './router'; export const examplePlugin = createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -183,7 +183,7 @@ that needs to be different across environments. import { coreServices } from '@backstage/backend-plugin-api'; export const examplePlugin = createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { config: coreServices.config }, @@ -216,7 +216,7 @@ export interface ExampleOptions { export const examplePlugin = createBackendPlugin( (options?: ExampleOptions) => ({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { diff --git a/docs/backend-system/building-plugins-and-modules/08-migrating.md b/docs/backend-system/building-plugins-and-modules/08-migrating.md index ec5836a126..687f4c1d5e 100644 --- a/docs/backend-system/building-plugins-and-modules/08-migrating.md +++ b/docs/backend-system/building-plugins-and-modules/08-migrating.md @@ -46,7 +46,7 @@ import { Router } from 'express'; import { KubernetesBuilder } from './KubernetesBuilder'; export const kubernetesPlugin = createBackendPlugin({ - id: 'kubernetes', + pluginId: 'kubernetes', register(env) { env.registerInit({ deps: { @@ -95,7 +95,7 @@ export interface KubernetesOptions { } const kubernetesPlugin = createBackendPlugin((options: KubernetesOptions) => ({ - id: 'kubernetes', + pluginId: 'kubernetes', register(env) { env.registerInit({ deps: { @@ -182,7 +182,7 @@ class ClusterSupplier implements KubernetesClusterSupplierExtensionPoint { } export const kubernetesPlugin = createBackendPlugin({ - id: 'kubernetes', + pluginId: 'kubernetes', register(env) { const extensionPoint = new ClusterSupplier(); // We register the extension point with the backend, which allows modules to diff --git a/docs/backend-system/core-services/01-index.md b/docs/backend-system/core-services/01-index.md index b394391480..5128a95cb3 100644 --- a/docs/backend-system/core-services/01-index.md +++ b/docs/backend-system/core-services/01-index.md @@ -33,7 +33,7 @@ import { import { Router } from 'express'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { http: coreServices.httpRouter }, @@ -86,7 +86,7 @@ import { import { Router } from 'express'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -157,7 +157,7 @@ import { } from '@backstage/backend-plugin-api'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -215,7 +215,7 @@ import { } from '@backstage/backend-plugin-api'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -288,7 +288,7 @@ import { } from '@backstage/backend-plugin-api'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -327,7 +327,7 @@ import { import { resolvePackagePath } from '@backstage/backend-common'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -366,7 +366,7 @@ import { import { fetch } from 'node-fetch'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -397,7 +397,7 @@ import { import { Router } from 'express'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -465,7 +465,7 @@ import { } from '@backstage/backend-plugin-api'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -479,10 +479,7 @@ createBackendPlugin({ // do some other stuff. }, 1000); - lifecycle.addShutdownHook({ - fn: () => clearInterval(interval), - logger, - }); + lifecycle.addShutdownHook(() => clearInterval(interval)); }, }); }, @@ -499,17 +496,19 @@ The following example shows how to override the default implementation of the li ```ts class MyCustomLifecycleService implements RootLifecycleService { - constructor(private readonly logger: LoggerService) { - ['SIGKILL', 'SIGTERM'].map(signal => - process.on(signal, () => this.shutdown()), - ); - } + constructor(private readonly logger: LoggerService) {} #isCalled = false; - #shutdownTasks: Array = []; + #shutdownTasks: Array<{ + hook: LifecycleServiceShutdownHook; + options?: LifecycleServiceShutdownOptions; + }> = []; - addShutdownHook(options: LifecycleServiceShutdownHook): void { - this.#shutdownTasks.push(options); + addShutdownHook( + hook: LifecycleServiceShutdownHook, + options?: LifecycleServiceShutdownOptions, + ): void { + this.#shutdownTasks.push({ hook, options }); } async shutdown(): Promise { @@ -520,10 +519,10 @@ class MyCustomLifecycleService implements RootLifecycleService { this.logger.info(`Running ${this.#shutdownTasks.length} shutdown tasks...`); await Promise.all( - this.#shutdownTasks.map(async hook => { - const { logger = this.logger } = hook; + this.#shutdownTasks.map(async ({ hook, options }) => { + const logger = options?.logger ?? this.logger; try { - await hook.fn(); + await hook(); logger.info(`Shutdown hook succeeded`); } catch (error) { logger.error(`Shutdown hook failed, ${error}`); @@ -564,7 +563,7 @@ import { import { Router } from 'express'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -613,7 +612,7 @@ import { import { fetch } from 'node-fetch'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { @@ -652,7 +651,7 @@ import { import os from 'os'; createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env) { env.registerInit({ deps: { diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 71aa0ab758..a272f05f3f 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -112,11 +112,11 @@ steps which would be rendered as different steps in the scaffolder plugin frontend. Each `Step` is `JSONSchema` with some extra goodies for styling what it might -look like in the frontend. For these steps we rely very heavily on this library: -https://github.com/rjsf-team/react-jsonschema-form. They have some great docs -too here: https://react-jsonschema-form.readthedocs.io/ and a playground where -you can play around with some examples here -https://rjsf-team.github.io/react-jsonschema-form. +look like in the frontend. For these steps we rely very heavily on this +[library](https://github.com/rjsf-team/react-jsonschema-form). They have some +[great docs](https://rjsf-team.github.io/react-jsonschema-form/docs/) and a +[playground](https://rjsf-team.github.io/react-jsonschema-form) where you can +play around with some examples. There's another option for that library called `uiSchema` which we've taken advantage of, and we've merged it with the existing `JSONSchema` that you diff --git a/docs/integrations/azure/locations.md b/docs/integrations/azure/locations.md index 3b3e3dd5ed..1fc476f418 100644 --- a/docs/integrations/azure/locations.md +++ b/docs/integrations/azure/locations.md @@ -28,3 +28,5 @@ The configuration is a structure with two elements: - `host`: The DevOps host; only `dev.azure.com` is supported. - `token` (optional): A personal access token as expected by Azure DevOps. + +> Note: The token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index dc0b911b3d..51c34b67ac 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -22,7 +22,7 @@ catalog: yourProviderId: host: gitlab-host # Identifies one of the hosts set up in the integrations branch: main # Optional. Uses `master` as default - group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole project will be scanned + group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole instance will be scanned entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` projectPattern: /[\s\S]*/ # Optional. Filters found projects based on provided patter. Defaults to `/[\s\S]*/`, what means to not filter anything schedule: # optional; same options as in TaskScheduleDefinition diff --git a/docs/permissions/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md index 04e31b859e..a38b81e679 100644 --- a/docs/permissions/plugin-authors/01-setup.md +++ b/docs/permissions/plugin-authors/01-setup.md @@ -48,7 +48,7 @@ The source code is available here: Create a new `packages/backend/src/plugins/todolist.ts` with the following content: - ```javascript + ```typescript import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; import { createRouter } from '@internal/plugin-todo-list-backend'; import { Router } from 'express'; diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 241b175987..2ceb8057f4 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -146,7 +146,7 @@ In order to test the logic above, the integrators of your backstage instance nee - async handle(): Promise { + async handle( + request: PolicyQuery, -+ user?: BackstageIdentityResponse, ++ _user?: BackstageIdentityResponse, + ): Promise { + if (isPermission(request.permission, todoListCreatePermission)) { + return { @@ -204,7 +204,7 @@ First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: const router = await createRouter({ logger: getVoidLogger(), identity: {} as DefaultIdentityClient, -+ permissions: toPermissionEvaluator, ++ permissions: permissionEvaluator, }); app = express().use(router); }); @@ -235,7 +235,7 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ + ServerTokenManager, } from '@backstage/backend-common'; import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; - import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++ import { ServerPermissionClient } from '@backstage/plugin-permission-node'; import { Server } from 'http'; import { Logger } from 'winston'; import { createRouter } from './router'; @@ -285,4 +285,44 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ module.hot?.accept(); ``` +Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`: + +```diff + import { loggerToWinstonLogger } from '@backstage/backend-common'; + import { + coreServices, + createBackendPlugin, + } from '@backstage/backend-plugin-api'; + import { createRouter } from './service/router'; + + /** + * The example TODO list backend plugin. + * + * @alpha + */ + export const exampleTodoListPlugin = createBackendPlugin({ + pluginId: 'exampleTodoList', + register(env) { + env.registerInit({ + deps: { + identity: coreServices.identity, + logger: coreServices.logger, + httpRouter: coreServices.httpRouter, ++ permissions: coreServices.permissions, + }, +- async init({ identity, logger, httpRouter }) { ++ async init({ identity, logger, httpRouter, permissions }) { + httpRouter.use( + await createRouter({ + identity, + logger: loggerToWinstonLogger(logger), + permissions, + }), + ); + }, + }); + }, + }); +``` + Now when you run `yarn tsc` you should have no more errors. diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index 8913b6d438..b063727c71 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -76,7 +76,7 @@ This enables decisions based on characteristics of the resource, but it's import Install the missing module: ``` -$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node +$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node zod ``` Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: @@ -89,7 +89,8 @@ import { Todo, TodoFilter } from './todos'; export const createTodoListPermissionRule = makeCreatePermissionRule< Todo, - TodoFilter + TodoFilter, + typeof TODO_LIST_RESOURCE_TYPE >(); export const isOwner = createTodoListPermissionRule({ @@ -97,8 +98,8 @@ export const isOwner = createTodoListPermissionRule({ description: 'Should allow only if the todo belongs to the user', resourceType: TODO_LIST_RESOURCE_TYPE, paramsSchema: z.object({ - userId: z.string().describe('User ID to match on the resource') - }) + userId: z.string().describe('User ID to match on the resource'), + }), apply: (resource: Todo, { userId }) => { return resource.author === userId; }, @@ -187,6 +188,7 @@ Make sure `todoListConditions` and `createTodoListConditionalDecision` are expor ```diff export * from './service/router'; + export * from './conditionExports'; + export { exampleTodoListPlugin } from './plugin'; ``` ## Test the authorized update endpoint @@ -209,7 +211,6 @@ Let's go back to the permission policy's handle function and try to authorize ou + import { + todoListCreatePermission, + todoListUpdatePermission, -+ TODO_LIST_RESOURCE_TYPE, + } from '@internal/plugin-todo-list-common'; + import { + todoListConditions, @@ -217,7 +218,11 @@ Let's go back to the permission policy's handle function and try to authorize ou + } from '@internal/plugin-todo-list-backend'; ... - + async handle( + request: PolicyQuery, +- _user?: BackstageIdentityResponse, ++ user?: BackstageIdentityResponse, + ): Promise { if (isPermission(request.permission, todoListCreatePermission)) { return { result: AuthorizeResult.ALLOW, @@ -236,6 +241,7 @@ Let's go back to the permission policy's handle function and try to authorize ou return { result: AuthorizeResult.ALLOW, }; + } ``` For any incoming update requests, we now return a _Conditional Decision_. We are saying: diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index 8ddcffdcd9..e84f53f176 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -15,7 +15,7 @@ One possible solution may leverage the batching functionality to authorize all o - res.json(getAll()) + const items = getAll(); + const decisions = await permissions.authorize( -+ items.map(({ id }) => ({ permission: todosListRead, resourceRef: id })), ++ items.map(({ id }) => ({ permission: todoListReadPermission, resourceRef: id })), + ); + const filteredItems = decisions.filter( @@ -53,7 +53,7 @@ Let's add another permission to the plugin. resourceType: TODO_LIST_RESOURCE_TYPE, }); + -+ export const todosListRead = createPermission({ ++ export const todoListReadPermission = createPermission({ + name: 'todos.list.read', + attributes: { action: 'read' }, + resourceType: TODO_LIST_RESOURCE_TYPE, @@ -81,10 +81,9 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev import { todosListCreate, todosListUpdate, -+ todosListRead, ++ todoListReadPermission, TODO_LIST_RESOURCE_TYPE, } from './permissions'; -+ import { rules } from './rules'; + const transformConditions: ConditionTransformer = createConditionTransformer(Object.values(rules)); @@ -95,7 +94,7 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev + ); + + const decision = ( -+ await permissions.authorizeConditional([{ permission: todosListRead }], { ++ await permissions.authorizeConditional([{ permission: todoListReadPermission }], { + token, + }) + )[0]; @@ -110,7 +109,6 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev + } else { + res.json(getAll()); + } -+ } - res.json(getAll()); }); ``` @@ -121,7 +119,7 @@ Since `TodoFilter` used in our plugin matches the structure of the conditions ob ## Test the authorized read endpoint -Let's update our permission policy to return a conditional result whenever a `todosListRead` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission. +Let's update our permission policy to return a conditional result whenever a `todoListReadPermission` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission. ```diff // packages/backend/src/plugins/permission.ts @@ -132,7 +130,6 @@ import { todoListCreatePermission, todoListUpdatePermission, + todoListReadPermission, - TODO_LIST_RESOURCE_TYPE, } from '@internal/plugin-todo-list-common'; ... diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md index d494ea60ba..753ce56461 100644 --- a/docs/permissions/plugin-authors/05-frontend-authorization.md +++ b/docs/permissions/plugin-authors/05-frontend-authorization.md @@ -56,7 +56,11 @@ Let's make the following changes in `plugins/todo-list/src/components/TodoListPa - Add - + {!loadingPermission && ( -+ + )} @@ -118,7 +122,10 @@ Providing a disabled state can be a helpful signal to users, but there may be ca - - - -+ }> ++ } ++ > + + + @@ -149,11 +156,15 @@ Providing a disabled state can be a helpful signal to users, but there may be ca onChange={e => (title.current = e.target.value)} /> - {!loadingPermission && ( -- - )} -+ diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index bfc6f79661..785d364ae2 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -84,7 +84,7 @@ import { // export type ExamplePluginOptions = { exampleOption: boolean }; export const examplePlugin = createBackendPlugin({ // unique id for the plugin - id: 'example', + pluginId: 'example', // It's possible to provide options to the plugin // register(env, options: ExamplePluginOptions) { register(env) { @@ -111,7 +111,7 @@ If we wanted our plugin to accept options as well, we'd accept the options as th ```ts export const examplePlugin = createBackendPlugin({ - id: 'example', + pluginId: 'example', register(env, options?: { silent?: boolean }) { env.registerInit({ deps: { logger: coreServices.logger }, diff --git a/microsite-next/yarn.lock b/microsite-next/yarn.lock index 5f27b0f062..d81c348bc3 100644 --- a/microsite-next/yarn.lock +++ b/microsite-next/yarn.lock @@ -6051,9 +6051,9 @@ __metadata: linkType: hard "http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 languageName: node linkType: hard @@ -8361,11 +8361,11 @@ __metadata: linkType: hard "prettier@npm:^2.6.2": - version: 2.8.1 - resolution: "prettier@npm:2.8.1" + version: 2.8.2 + resolution: "prettier@npm:2.8.2" bin: prettier: bin-prettier.js - checksum: 4f21a0f1269f76fb36f54e9a8a1ea4c11e27478958bf860661fb4b6d7ac69aac1581f8724fa98ea3585e56d42a2ea317a17ff6e3324f40cb11ff9e20b73785cc + checksum: 740c56c2128d587d656ea1dde9bc9c3503dfc94db4f3ac387259215eeb2e216680bdad9d18a0c9feecc6b42cfa188d6fa777df4c36c1d00cedd4199074fbfbd2 languageName: node linkType: hard diff --git a/microsite/blog/2023-01-31-incremental-entity-provider.md b/microsite/blog/2023-01-31-incremental-entity-provider.md index ff5f346ccd..c14c3fc15d 100644 --- a/microsite/blog/2023-01-31-incremental-entity-provider.md +++ b/microsite/blog/2023-01-31-incremental-entity-provider.md @@ -4,8 +4,6 @@ author: Paul Cowan & Taras Mankovski authorURL: https://frontside.com/ --- -# Scaling Backstage Ingestion with Incremental Entity Providers - At the heart of [Backstage](https://backstage.io/) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store. ![catalog pipeline](assets/2023-01-31/catalog-pipeline.png) diff --git a/microsite/blog/2023-02-06-whats-ahead-in-2023.md b/microsite/blog/2023-02-06-whats-ahead-in-2023.md new file mode 100644 index 0000000000..a9f06d9ea0 --- /dev/null +++ b/microsite/blog/2023-02-06-whats-ahead-in-2023.md @@ -0,0 +1,58 @@ +--- +title: What’s Ahead in 2023? +author: Lee Mills +authorURL: https://github.com/leemills83 +--- + +# What’s Ahead in 2023? + +![Fewer “vibes” and more climbs (up the contribution ladder)](assets/2023-02-06/whats-ahead.png) + +Okay, everyone. Time to move on from all the 2022 reflections, Top 10 lists, and Wrapped(s), and turn our gaze in the other direction — towards 2023. As the Backstage team moves into the new year, we find ourselves asking those perennial questions: How can we continue to grow and enhance the Backstage community? How can we improve the experience for those adopting Backstage? How can we give the best support to Backstage contributors? + +Well, we don’t have all the answers, but we do have some. So we wanted to share more on three ongoing initiatives aimed at moving the needle forward for these three distinct, yet interrelated, questions. + +- Grow your influence by climbing the contribution ladder +- Come join our SIGs (and push for new ones) +- Work with us to drive a better experience for Backstage adopters + +# Climb the contribution ladder + +Truth be told, we’ve been wanting to grow the maintainer team for some time, and have had multiple requests from contributors and contributing organizations wanting to become maintainers. But, so far, we just haven’t managed to get anything off the ground. What we see is that becoming a maintainer is too hard and the current ownership model doesn’t work. In short, we need to update Backstage governance. + +To address this, we are proposing (RFC links below) the following three initiatives: + +- Introduce more steps in between first-time contributor and maintainer in the form of a contributor ladder (loosely based on the [CNCF Project Template](https://github.com/cncf/project-template/blob/main/CONTRIBUTOR_LADDER.md)). The ladder might look as follows: Contributor, Org Member, Reviewer, Maintainer, Project Maintainer. +- Change the ownership model, for example, divide the project up into several project areas, each covering particular parts of the project. +- Introduce a new process for large contributions in the form of Backstage Enhancement Proposals (BEPs). + +Read more and join the already rich discussion in [RFC: Updated Backstage Governance](https://github.com/backstage/backstage/issues/15317) and [RFC: Backstage Enhancement Proposals](https://github.com/backstage/backstage/issues/15318). The RFC will be open until 13/02/2023. + +# Come join our SIGs (and push for new ones) + +As many of you know, SIG stands for Special Interest Group. The purpose of a SIG is to bring together maintainers and interested contributors within a certain specialist area to discuss, debate, spread knowledge, brainstorm - and drive the overall project forward. They’re also a lot of fun! + +We currently have two SIGs up and running: + +- [Catalog](https://github.com/backstage/community/tree/main/sigs/sig-catalog?utm_campaign=Newsletters%20Sent&utm_medium=email&_hsmi=241130993&_hsenc=p2ANqtz-9fx-nTmQga2j12NqzZaL-lYPq-xQjDoXcWKe_JUoa2RGlK65R9j0CIygfYILr0tGEQSvRVN9xzhiJ6LmVxBthKnStfSA&utm_content=241130993&utm_source=hs_email) started up last June and covers all aspects of the systems and components related to the Software Catalog. The group runs every second Tuesday between 16:00 and 17:00 CET. Just show up or hop into [#catalog](https://discord.com/channels/687207715902193673/923144214580191282) (Discord), if you want to talk to us first. Here are the [notes from our sessions so far](https://docs.google.com/document/d/1xg57CDfX2R5jTGbbWANzifm2szEb664kuTwfEEvKgZU/edit#heading=h.iw0hjv6xfb9e). +- [Adoption](https://github.com/backstage/community/tree/main/sigs/sig-adoption?utm_campaign=Newsletters%20Sent&utm_medium=email&_hsmi=241130993&_hsenc=p2ANqtz-9CAxtT8JSeJZPh41o3ZnDBi0VBFpbL6fEqXInr9HHOFhU3JAo8HekKg1JzE5rhGP5zOaHcsYDd1YxJWNHw-eSk5mLWkA&utm_content=241130993&utm_source=hs_email) kicked off in December and covers all aspects of the Backstage adoption journey including documentation and tutorials. In the first two meetups, we discussed adoption challenges based on user persona, collecting data about those challenges, and bootstrapping the Catalog to improve metadata quality for adopters. This one runs every second Thursday between 16:00 and 17:00 CET. Again just show up or, if you want to talk to us first, you can do so in [#adoption](https://discord.com/channels/687207715902193673/1045031039061479536) (Discord). Here are the [notes from our sessions](https://docs.google.com/document/d/1zeYSzXYgh897bxPQmwy2btOwCFlEjlUDLf00riySLRc/edit?_hsmi=241130993&_hsenc=p2ANqtz-_nPuE5hW4fnbhJ5-uK8r5HisSTU7rkslKirvqu0g-vdynbitPVYF2AVLAatos2dLoxb2Bgx_g4LBgH02kXoq7_Acd43Q#heading=h.iw0hjv6xfb9e) so far. + +So that’s SIGs. Get involved and join the ones we have up and running. And/or feel free to propose new ones. + +# Help drive a better experience for all adopters + +Throughout all stages of adopting Backstage in an organization — initial discovery, evaluation and POC, and scaling to the wider org — we have identified opportunities where we want to make the journey for Backstage adopters faster and smoother. + +We’ll go step-by-step, but initial plans are to: + +- Make it easier to install, customize, and maintain Backstage +- Provide Golden Paths for adoption +- Improve the experience and the content on [backstage.io](https://backstage.io/docs/overview/what-is-backstage) + +As we move forward, we’d love you to be part of it. Here are some ways that you can contribute to this initiative: + +- Simply give us feedback. [Tell us about your adoption journey](https://spotify.co1.qualtrics.com/jfe/form/SV_cuxhYPB6oQDu7gW). +- Contribute to the pages and documentation on [backstage.io](https://backstage.io/docs/overview/what-is-backstage) +- Pay it forward by making code contributions even during your adoption journey (if you are on one) +- Join the [Adoption](https://github.com/backstage/community/tree/main/sigs/sig-adoption?utm_campaign=Newsletters%20Sent&utm_medium=email&_hsmi=241130993&_hsenc=p2ANqtz-9CAxtT8JSeJZPh41o3ZnDBi0VBFpbL6fEqXInr9HHOFhU3JAo8HekKg1JzE5rhGP5zOaHcsYDd1YxJWNHw-eSk5mLWkA&utm_content=241130993&utm_source=hs_email) SIG +- Chat with us on [#adoption](https://discord.com/channels/687207715902193673/1045031039061479536) (Discord) diff --git a/microsite/blog/assets/2023-02-06/whats-ahead.png b/microsite/blog/assets/2023-02-06/whats-ahead.png new file mode 100644 index 0000000000..e49f68149d Binary files /dev/null and b/microsite/blog/assets/2023-02-06/whats-ahead.png differ diff --git a/microsite/data/plugins/vault.yaml b/microsite/data/plugins/vault.yaml index 822f5dcc90..bb8f799ba7 100644 --- a/microsite/data/plugins/vault.yaml +++ b/microsite/data/plugins/vault.yaml @@ -3,7 +3,7 @@ title: Vault author: Spread Group authorUrl: https://github.com/ivangonzalezacuna category: Vault -description: Visualize a list secrets stored in your vault instance. +description: Visualize a list of the secrets stored in your HashiCorp Vault instance. documentation: https://github.com/backstage/backstage/tree/master/plugins/vault iconUrl: img/vault.png npmPackageName: '@backstage/plugin-vault' diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 3bc81619e3..34ed76e9e4 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -7369,11 +7369,11 @@ __metadata: linkType: hard "prettier@npm:^2.6.2": - version: 2.8.1 - resolution: "prettier@npm:2.8.1" + version: 2.8.2 + resolution: "prettier@npm:2.8.2" bin: prettier: bin-prettier.js - checksum: 4f21a0f1269f76fb36f54e9a8a1ea4c11e27478958bf860661fb4b6d7ac69aac1581f8724fa98ea3585e56d42a2ea317a17ff6e3324f40cb11ff9e20b73785cc + checksum: 740c56c2128d587d656ea1dde9bc9c3503dfc94db4f3ac387259215eeb2e216680bdad9d18a0c9feecc6b42cfa188d6fa777df4c36c1d00cedd4199074fbfbd2 languageName: node linkType: hard diff --git a/package.json b/package.json index 912dd53b8a..e08ca3adfd 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "create-plugin": "echo \"use 'yarn new' instead\"", "release": "node scripts/prepare-release.js && changeset version && yarn prettier --write '{packages,plugins}/*/{package.json,CHANGELOG.md}' '.changeset/*.json' && yarn install --no-immutable", "prettier:check": "prettier --check .", + "prettier:fix": "prettier --write .", "storybook": "yarn ./storybook run start", "snyk:test": "npx snyk test --yarn-workspaces --strict-out-of-sync=false", "snyk:test:package": "yarn snyk:test --include", diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json index 2b3a458b95..a467608916 100644 --- a/packages/app/cypress/.eslintrc.json +++ b/packages/app/cypress/.eslintrc.json @@ -7,15 +7,6 @@ { "assertFunctionNames": ["expect", "cy.contains"] } - ], - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": true, - "optionalDependencies": true, - "peerDependencies": true, - "bundledDependencies": true - } ] } } diff --git a/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts b/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts index 8baf84cb69..4d95f863db 100644 --- a/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts +++ b/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts @@ -26,7 +26,8 @@ export const databaseFactory = createServiceFactory({ service: coreServices.database, deps: { config: coreServices.config, - plugin: coreServices.pluginMetadata, + lifecycle: coreServices.lifecycle, + pluginMetadata: coreServices.pluginMetadata, }, async createRootContext({ config }) { return config.getOptional('backend.database') @@ -39,7 +40,10 @@ export const databaseFactory = createServiceFactory({ }), ); }, - async factory({ plugin }, databaseManager) { - return databaseManager.forPlugin(plugin.getId()); + async factory({ pluginMetadata, lifecycle }, databaseManager) { + return databaseManager.forPlugin(pluginMetadata.getId(), { + pluginMetadata, + lifecycle, + }); }, }); diff --git a/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleFactory.ts b/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleFactory.ts index 299022079b..27c1a139fd 100644 --- a/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleFactory.ts +++ b/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleFactory.ts @@ -17,6 +17,7 @@ import { createServiceFactory, coreServices, LifecycleServiceShutdownHook, + LifecycleServiceShutdownOptions, } from '@backstage/backend-plugin-api'; /** @@ -33,11 +34,12 @@ export const lifecycleFactory = createServiceFactory({ async factory({ rootLifecycle, logger, pluginMetadata }) { const plugin = pluginMetadata.getId(); return { - addShutdownHook(options: LifecycleServiceShutdownHook): void { - rootLifecycle.addShutdownHook({ - ...options, - - logger: options.logger?.child({ plugin }) ?? logger, + addShutdownHook( + hook: LifecycleServiceShutdownHook, + options?: LifecycleServiceShutdownOptions, + ): void { + rootLifecycle.addShutdownHook(hook, { + logger: options?.logger?.child({ plugin }) ?? logger, }); }, }; diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts index 58f8cc2db1..f46f92741b 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts @@ -104,12 +104,7 @@ export const rootHttpRouterFactory = createServiceFactory({ { logger }, ); - lifecycle.addShutdownHook({ - async fn() { - await server.stop(); - }, - logger, - }); + lifecycle.addShutdownHook(() => server.stop()); await server.start(); diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleFactory.test.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleFactory.test.ts index fc1c266775..3a93cacc8d 100644 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleFactory.test.ts @@ -21,11 +21,7 @@ describe('lifecycleService', () => { it('should execute registered shutdown hook', async () => { const service = new BackendLifecycleImpl(getVoidLogger()); const hook = jest.fn(); - service.addShutdownHook({ - fn: async () => { - hook(); - }, - }); + service.addShutdownHook(() => hook()); // should not execute the hook more than once. await service.shutdown(); await service.shutdown(); @@ -35,10 +31,16 @@ describe('lifecycleService', () => { it('should not throw errors', async () => { const service = new BackendLifecycleImpl(getVoidLogger()); - service.addShutdownHook({ - fn: async () => { - throw new Error('oh no'); - }, + service.addShutdownHook(() => { + throw new Error('oh no'); + }); + await expect(service.shutdown()).resolves.toBeUndefined(); + }); + + it('should not throw async errors', async () => { + const service = new BackendLifecycleImpl(getVoidLogger()); + service.addShutdownHook(async () => { + throw new Error('oh no'); }); await expect(service.shutdown()).resolves.toBeUndefined(); }); diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleFactory.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleFactory.ts index 71ab76ae92..2b6f7eeafe 100644 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleFactory.ts +++ b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleFactory.ts @@ -18,21 +18,25 @@ import { createServiceFactory, coreServices, LifecycleServiceShutdownHook, + LifecycleServiceShutdownOptions, RootLifecycleService, LoggerService, } from '@backstage/backend-plugin-api'; -const CALLBACKS = ['SIGTERM', 'SIGINT', 'beforeExit']; export class BackendLifecycleImpl implements RootLifecycleService { - constructor(private readonly logger: LoggerService) { - CALLBACKS.map(signal => process.on(signal, () => this.shutdown())); - } + constructor(private readonly logger: LoggerService) {} #isCalled = false; - #shutdownTasks: Array = []; + #shutdownTasks: Array<{ + hook: LifecycleServiceShutdownHook; + options?: LifecycleServiceShutdownOptions; + }> = []; - addShutdownHook(options: LifecycleServiceShutdownHook): void { - this.#shutdownTasks.push(options); + addShutdownHook( + hook: LifecycleServiceShutdownHook, + options?: LifecycleServiceShutdownOptions, + ): void { + this.#shutdownTasks.push({ hook, options }); } async shutdown(): Promise { @@ -43,10 +47,10 @@ export class BackendLifecycleImpl implements RootLifecycleService { this.logger.info(`Running ${this.#shutdownTasks.length} shutdown tasks...`); await Promise.all( - this.#shutdownTasks.map(async hook => { - const { logger = this.logger } = hook; + this.#shutdownTasks.map(async ({ hook, options }) => { + const logger = options?.logger ?? this.logger; try { - await hook.fn(); + await hook(); logger.info(`Shutdown hook succeeded`); } catch (error) { logger.error(`Shutdown hook failed, ${error}`); diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.test.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.test.ts index 1aaf4ec71d..15ec364ac5 100644 --- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.test.ts @@ -28,7 +28,7 @@ describe('schedulerFactory', () => { const subject = schedulerFactory(); const plugin = createBackendPlugin({ - id: 'example', + pluginId: 'example', register(reg) { reg.registerInit({ deps: { diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index ce17398a79..4bdee8b085 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -28,7 +28,7 @@ import { } from './types'; export class BackendInitializer { - #started = false; + #startPromise?: Promise; #features = new Map(); #registerInits = new Array(); #extensionPoints = new Map, unknown>(); @@ -75,18 +75,40 @@ export class BackendInitializer { } add(feature: BackendFeature, options?: TOptions) { - if (this.#started) { + if (this.#startPromise) { throw new Error('feature can not be added after the backend has started'); } this.#features.set(feature, options); } async start(): Promise { - if (this.#started) { + if (this.#startPromise) { throw new Error('Backend has already started'); } - this.#started = true; + const exitHandler = async () => { + process.removeListener('SIGTERM', exitHandler); + process.removeListener('SIGINT', exitHandler); + process.removeListener('beforeExit', exitHandler); + + try { + await this.stop(); + process.exit(0); + } catch (error) { + console.error(error); + process.exit(1); + } + }; + + process.addListener('SIGTERM', exitHandler); + process.addListener('SIGINT', exitHandler); + process.addListener('beforeExit', exitHandler); + + this.#startPromise = this.#doStart(); + await this.#startPromise; + } + + async #doStart(): Promise { // Initialize all root scoped services for (const ref of this.#serviceHolder.getServiceRefs()) { if (ref.scope === 'root') { @@ -177,9 +199,10 @@ export class BackendInitializer { } async stop(): Promise { - if (!this.#started) { + if (!this.#startPromise) { return; } + await this.#startPromise; const lifecycleService = await this.#serviceHolder.get( coreServices.rootLifecycle, diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index b03d6ec866..e77a58b34c 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -32,6 +32,7 @@ import { IdentityService } from '@backstage/backend-plugin-api'; import { isChildPath } from '@backstage/cli-common'; import { Knex } from 'knex'; import { KubeConfig } from '@kubernetes/client-node'; +import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoadConfigOptionsRemote } from '@backstage/config-loader'; import { Logger } from 'winston'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -40,6 +41,7 @@ import { PermissionsService } from '@backstage/backend-plugin-api'; import { CacheService as PluginCacheManager } from '@backstage/backend-plugin-api'; import { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; import { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api'; +import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { PushResult } from 'isomorphic-git'; import { Readable } from 'stream'; import { ReadCommitResult } from 'isomorphic-git'; @@ -232,6 +234,10 @@ export class Contexts { export function createDatabaseClient( dbConfig: Config, overrides?: Partial, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, ): Knex; // @public @@ -252,7 +258,13 @@ export function createStatusCheckRouter(options: { // @public export class DatabaseManager { - forPlugin(pluginId: string): PluginDatabaseManager; + forPlugin( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): PluginDatabaseManager; static fromConfig( config: Config, options?: DatabaseManagerOptions, diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index c950950680..9f1069f322 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -35,6 +35,7 @@ }, "dependencies": { "@backstage/backend-app-api": "workspace:^", + "@backstage/backend-dev-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/cli-common": "workspace:^", "@backstage/config": "workspace:^", diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index 5a497fff29..08979a92f8 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -29,7 +29,11 @@ import { } from './connection'; import { PluginDatabaseManager } from './types'; import path from 'path'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LifecycleService, + LoggerService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; import { stringifyError } from '@backstage/errors'; /** @@ -94,12 +98,18 @@ export class DatabaseManager { * should be unique as they are used to look up database config overrides under * `backend.database.plugin`. */ - forPlugin(pluginId: string): PluginDatabaseManager { + forPlugin( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): PluginDatabaseManager { const _this = this; return { getClient(): Promise { - return _this.getDatabase(pluginId); + return _this.getDatabase(pluginId, deps); }, migrations: { skip: false, @@ -307,7 +317,13 @@ export class DatabaseManager { * @returns Promise which resolves to a scoped Knex database client for a * plugin */ - private async getDatabase(pluginId: string): Promise { + private async getDatabase( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Promise { if (this.databaseCache.has(pluginId)) { return this.databaseCache.get(pluginId)!; } @@ -351,6 +367,7 @@ export class DatabaseManager { const client = createDatabaseClient( pluginConfig, databaseClientOverrides, + deps, ); this.startKeepaliveLoop(pluginId, client); return client; diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts index 5f487886b4..ff10d349dd 100644 --- a/packages/backend-common/src/database/connection.ts +++ b/packages/backend-common/src/database/connection.ts @@ -22,6 +22,10 @@ import { mergeDatabaseConfig } from './config'; import { DatabaseConnector } from './types'; import { mysqlConnector, pgConnector, sqlite3Connector } from './connectors'; +import { + LifecycleService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; type DatabaseClient = | 'pg' @@ -55,11 +59,15 @@ const ConnectorMapping: Record = { export function createDatabaseClient( dbConfig: Config, overrides?: Partial, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, ) { const client: DatabaseClient = dbConfig.getString('client'); return ( - ConnectorMapping[client]?.createClient(dbConfig, overrides) ?? + ConnectorMapping[client]?.createClient(dbConfig, overrides, deps) ?? knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides)) ); } diff --git a/packages/backend-common/src/database/connectors/sqlite3.ts b/packages/backend-common/src/database/connectors/sqlite3.ts index 6426005f0e..3cb0f57de6 100644 --- a/packages/backend-common/src/database/connectors/sqlite3.ts +++ b/packages/backend-common/src/database/connectors/sqlite3.ts @@ -18,8 +18,13 @@ import { Config } from '@backstage/config'; import { ensureDirSync } from 'fs-extra'; import knexFactory, { Knex } from 'knex'; import path from 'path'; +import { DevDataStore } from '@backstage/backend-dev-utils'; import { mergeDatabaseConfig } from '../config'; import { DatabaseConnector } from '../types'; +import { + LifecycleService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; /** * Creates a knex SQLite3 database connection @@ -30,22 +35,63 @@ import { DatabaseConnector } from '../types'; export function createSqliteDatabaseClient( dbConfig: Config, overrides?: Knex.Config, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, ) { const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides); + const connConfig = knexConfig.connection as Knex.Sqlite3ConnectionConfig; + + const filename = connConfig.filename ?? ':memory:'; // If storage on disk is used, ensure that the directory exists - if ( - (knexConfig.connection as Knex.Sqlite3ConnectionConfig).filename && - (knexConfig.connection as Knex.Sqlite3ConnectionConfig).filename !== - ':memory:' - ) { - const { filename } = knexConfig.connection as Knex.Sqlite3ConnectionConfig; + if (filename !== ':memory:') { const directory = path.dirname(filename); - ensureDirSync(directory); } - const database = knexFactory(knexConfig); + let database: Knex; + + if (deps && filename === ':memory:') { + // The dev store is used during watch mode to store and restore the database + // across reloads. It is only available when running the backend through + // `backstage-cli package start`. + const devStore = DevDataStore.get(); + + if (devStore) { + const dataKey = `sqlite3-db-${deps.pluginMetadata.getId()}`; + + const connectionLoader = async () => { + // If seed data is available, use it tconnectionLoader restore the database + const { data: seedData } = await devStore.load(dataKey); + + return { + ...(knexConfig.connection as Knex.Sqlite3ConnectionConfig), + filename: seedData ?? ':memory:', + }; + }; + + database = knexFactory({ + ...knexConfig, + connection: Object.assign(connectionLoader, { + // This is a workaround for the knex SQLite driver always warning when using a config loader + filename: ':memory:', + }), + }); + + // If the dev store is available we save the database state on shutdown + deps.lifecycle.addShutdownHook(async () => { + const connection = await database.client.acquireConnection(); + const data = connection.serialize(); + await devStore.save(dataKey, data); + }); + } else { + database = knexFactory(knexConfig); + } + } else { + database = knexFactory(knexConfig); + } database.client.pool.on('createSuccess', (_eventId: any, resource: any) => { resource.run('PRAGMA foreign_keys = ON', () => {}); diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index 2d59cf636e..621bcc676b 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + LifecycleService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { Knex } from 'knex'; @@ -26,7 +30,14 @@ export interface DatabaseConnector { /** * createClient provides an instance of a knex database connector. */ - createClient(dbConfig: Config, overrides?: Partial): Knex; + createClient( + dbConfig: Config, + overrides?: Partial, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Knex; /** * createNameOverride provides a partial knex config sufficient to override a * database name. diff --git a/packages/backend-common/src/legacy.ts b/packages/backend-common/src/legacy.ts index 30e7dd9dc5..214e65e0c7 100644 --- a/packages/backend-common/src/legacy.ts +++ b/packages/backend-common/src/legacy.ts @@ -60,7 +60,7 @@ export function makeLegacyPlugin< }>, ) => { const compatPlugin = createBackendPlugin({ - id: name, + pluginId: name, register(env) { env.registerInit({ deps: { ...envMapping, _router: coreServices.httpRouter }, diff --git a/packages/backend-common/src/scm/git.test.ts b/packages/backend-common/src/scm/git.test.ts index fdd3c7dbc8..629173f96e 100644 --- a/packages/backend-common/src/scm/git.test.ts +++ b/packages/backend-common/src/scm/git.test.ts @@ -202,7 +202,7 @@ describe('Git', () => { await git.clone({ url, dir }); const { onAuth } = ( - isomorphic.clone as unknown as jest.Mock + isomorphic.clone as unknown as jest.Mock<(typeof isomorphic)['clone']> ).mock.calls[0][0]!; expect(onAuth()).toEqual(auth); @@ -315,7 +315,7 @@ describe('Git', () => { await git.fetch({ remote, dir }); const { onAuth } = ( - isomorphic.fetch as unknown as jest.Mock + isomorphic.fetch as unknown as jest.Mock<(typeof isomorphic)['fetch']> ).mock.calls[0][0]!; expect(onAuth()).toEqual(auth); @@ -486,7 +486,7 @@ describe('Git', () => { await git.push({ remote, dir, remoteRef, force }); const { onAuth } = ( - isomorphic.push as unknown as jest.Mock + isomorphic.push as unknown as jest.Mock<(typeof isomorphic)['push']> ).mock.calls[0][0]!; expect(onAuth()).toEqual(auth); diff --git a/packages/backend-defaults/src/CreateBackend.test.ts b/packages/backend-defaults/src/CreateBackend.test.ts index 2cfb92c3a6..8d56cd0986 100644 --- a/packages/backend-defaults/src/CreateBackend.test.ts +++ b/packages/backend-defaults/src/CreateBackend.test.ts @@ -143,7 +143,7 @@ describe('createBackend', () => { expect.assertions(3); backend.add( createBackendPlugin({ - id: 'test', + pluginId: 'test', register(reg) { reg.registerInit({ deps: { diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index e6b5a15336..5cbed2c3df 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -40,7 +40,7 @@ import { } from '@backstage/backend-plugin-api'; // Internal import of the type to avoid needing to export this. -// eslint-disable-next-line monorepo/no-internal-import +// eslint-disable-next-line @backstage/no-forbidden-package-imports import type { InternalSharedBackendEnvironment } from '@backstage/backend-plugin-api/src/wiring/createSharedEnvironment'; export const defaultServiceFactories = [ diff --git a/packages/backend-dev-utils/.eslintrc.js b/packages/backend-dev-utils/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/backend-dev-utils/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/backend-dev-utils/README.md b/packages/backend-dev-utils/README.md new file mode 100644 index 0000000000..8acce94cf0 --- /dev/null +++ b/packages/backend-dev-utils/README.md @@ -0,0 +1,5 @@ +# backend-dev-utils + +Welcome to the backend-dev-utils package! + +_This package is experimental._ diff --git a/packages/backend-dev-utils/api-report.md b/packages/backend-dev-utils/api-report.md new file mode 100644 index 0000000000..216311ccd8 --- /dev/null +++ b/packages/backend-dev-utils/api-report.md @@ -0,0 +1,20 @@ +## API Report File for "@backstage/backend-dev-utils" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// @public +export class DevDataStore { + static get(): DevDataStore | undefined; + load(key: string): Promise<{ + loaded: boolean; + data: T; + }>; + save( + key: string, + data: T, + ): Promise<{ + saved: boolean; + }>; +} +``` diff --git a/packages/backend-dev-utils/package.json b/packages/backend-dev-utils/package.json new file mode 100644 index 0000000000..735185e37f --- /dev/null +++ b/packages/backend-dev-utils/package.json @@ -0,0 +1,36 @@ +{ + "name": "@backstage/backend-dev-utils", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "node-library" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-dev-utils" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "devDependencies": { + "@backstage/cli": "workspace:^" + }, + "files": [ + "dist" + ] +} diff --git a/packages/backend-dev-utils/src/DevDataStore.test.ts b/packages/backend-dev-utils/src/DevDataStore.test.ts new file mode 100644 index 0000000000..2bb3f221ae --- /dev/null +++ b/packages/backend-dev-utils/src/DevDataStore.test.ts @@ -0,0 +1,121 @@ +/* + * Copyright 2023 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 { Serializable, spawn } from 'child_process'; +import { DevDataStore } from './DevDataStore'; +import { BackstageIpcClient } from './ipcClient'; + +function applyIpcTransform(value: Serializable): Promise { + const child = spawn( + 'node', + [ + '--eval', + ` + const listener = msg => { + process.send(msg); + process.removeListener('message', listener); + } + process.addListener('message', listener); + `, + ], + { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + serialization: 'advanced', + }, + ); + + return new Promise(resolve => { + child.addListener('message', resolve); + child.send(value); + }); +} + +class MockIpcClient implements Pick { + #data = new Map(); + + async request(method: string, body: any): Promise { + if (method === 'DevDataStore.save') { + this.#data.set(body.key, body.data); + return { saved: true }; + } else if (method === 'DevDataStore.load') { + if (!this.#data.has(body.key)) { + return { loaded: false, data: undefined }; + } + const data = this.#data.get(body.key)!; + return { loaded: true, data: await applyIpcTransform(data) }; + } + throw new Error('Unknown message'); + } +} + +describe('DevDataStore', () => { + it('should save and load data', async () => { + const store = DevDataStore.forTest(new MockIpcClient()); + + await expect(store.save('test', { foo: 'bar' })).resolves.toEqual({ + saved: true, + }); + await expect(store.load('test')).resolves.toEqual({ + loaded: true, + data: { foo: 'bar' }, + }); + }); + + it('should save and load buffers', async () => { + const store = DevDataStore.forTest(new MockIpcClient()); + + await expect( + store.save('test', Buffer.from('abc', 'ascii')), + ).resolves.toEqual({ + saved: true, + }); + await expect(store.load('test')).resolves.toEqual({ + loaded: true, + data: Buffer.from('abc', 'ascii'), + }); + }); + + it('should save and load array buffers', async () => { + const store = DevDataStore.forTest(new MockIpcClient()); + + await expect( + store.save('test', new Uint8Array(Buffer.from('abc', 'ascii'))), + ).resolves.toEqual({ + saved: true, + }); + await expect(store.load('test')).resolves.toEqual({ + loaded: true, + data: new Uint8Array(Buffer.from('abc', 'ascii')), + }); + }); + + it('should save and load buffers nested in objects', async () => { + const store = DevDataStore.forTest(new MockIpcClient()); + + await expect( + store.save('test', { + x: Buffer.from('x', 'ascii'), + y: [Buffer.from('y', 'ascii')], + }), + ).resolves.toEqual({ + saved: true, + }); + await expect(store.load('test')).resolves.toEqual({ + loaded: true, + data: { x: Buffer.from('x', 'ascii'), y: [Buffer.from('y', 'ascii')] }, + }); + }); +}); diff --git a/packages/backend-dev-utils/src/DevDataStore.ts b/packages/backend-dev-utils/src/DevDataStore.ts new file mode 100644 index 0000000000..4069ab1bdb --- /dev/null +++ b/packages/backend-dev-utils/src/DevDataStore.ts @@ -0,0 +1,99 @@ +/* + * Copyright 2023 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 { BackstageIpcClient, ipcClient } from './ipcClient'; + +interface SaveRequest { + key: string; + data: unknown; +} + +interface SaveResponse { + saved: boolean; +} + +interface LoadRequest { + key: string; +} + +interface LoadResponse { + loaded: boolean; + data: unknown; +} + +/** + * A data store that can be used to store temporary data during development. + * + * @public + */ +export class DevDataStore { + static #instance?: DevDataStore; + + /** + * Tries to acquire a DevDataStore instance. This will only succeed when the backend + * process is being run through the `@backstage/cli` in development mode. + * + * @returns A DevDataStore instance, or undefined if not available. + */ + static get(): DevDataStore | undefined { + if (ipcClient) { + if (!this.#instance) { + this.#instance = new DevDataStore(ipcClient); + } + return this.#instance; + } + return undefined; + } + + /** @internal */ + static forTest(client: Pick): DevDataStore { + return new DevDataStore(client as BackstageIpcClient); + } + + #client: BackstageIpcClient; + + private constructor(client: BackstageIpcClient) { + this.#client = client; + } + + /** + * Save data to the data store. + * + * @param key - The key used to identify the data. + * @param data - The data to save. The data will be serialized using advanced IPC serialization. + * @returns A promise that resolves to a result object that indicates whether the data was saved. + */ + async save(key: string, data: T): Promise<{ saved: boolean }> { + return this.#client.request( + 'DevDataStore.save', + { key, data }, + ); + } + + /** + * Loads data from the data store. + * + * @param key - The key used to identify the data. + * @returns A promise that resolves to a result object that indicates whether the data was loaded, as well as the data. + */ + async load(key: string): Promise<{ loaded: boolean; data: T }> { + const result = await this.#client.request( + 'DevDataStore.load', + { key }, + ); + return result as { loaded: boolean; data: T }; + } +} diff --git a/packages/backend-dev-utils/src/index.ts b/packages/backend-dev-utils/src/index.ts new file mode 100644 index 0000000000..9b707db029 --- /dev/null +++ b/packages/backend-dev-utils/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2023 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. + */ + +/** + * Local development helpers for backend. + * + * @packageDocumentation + */ + +export { DevDataStore } from './DevDataStore'; diff --git a/packages/backend-dev-utils/src/ipcClient.ts b/packages/backend-dev-utils/src/ipcClient.ts new file mode 100644 index 0000000000..87c1596a39 --- /dev/null +++ b/packages/backend-dev-utils/src/ipcClient.ts @@ -0,0 +1,123 @@ +/* + * Copyright 2023 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. + */ + +type SendMessage = Exclude; + +interface Request { + id: number; + method: string; + body: unknown; + type: string; +} + +type Response = + | { + type: string; + id: number; + body: unknown; + } + | { + type: string; + id: number; + error: Error; + }; + +const requestType = '@backstage/cli/channel/request'; +const responseType = '@backstage/cli/channel/response'; + +/** + * The client side of an IPC communication channel. + * + * @internal + */ +export class BackstageIpcClient { + #messageId = 0; + #sendMessage: SendMessage; + + /** + * Creates a new client if we're in a child process with IPC and BACKSTAGE_CLI_CHANNEL is set. + */ + static create(): BackstageIpcClient | undefined { + const sendMessage = process.send?.bind(process); + return sendMessage && process.env.BACKSTAGE_CLI_CHANNEL + ? new BackstageIpcClient(sendMessage) + : undefined; + } + + constructor(sendMessage: SendMessage) { + this.#sendMessage = sendMessage; + } + + /** + * Send a request to the parent process and wait for a response. + */ + async request( + method: string, + body: TRequestBody, + ): Promise { + return new Promise((resolve, reject) => { + const id = this.#messageId++; + + const request: Request = { + type: requestType, + id, + method, + body, + }; + + this.#sendMessage(request, (e: Error) => { + if (e) { + reject(e); + return; + } + + let timeout: NodeJS.Timeout | undefined = undefined; + + const messageHandler = (response: Response) => { + if (response?.type !== responseType) { + return; + } + if (response.id !== id) { + return; + } + + if ('error' in response) { + const error = new Error(response.error.message); + if (response.error.name) { + error.name = response.error.name; + } + reject(error); + } else { + resolve(response.body as TResponseBody); + } + + clearTimeout(timeout); + process.removeListener('message', messageHandler); + }; + + timeout = setTimeout(() => { + reject(new Error(`IPC request '${method}' with ID ${id} timed out`)); + process.removeListener('message', messageHandler); + }, 5000); + timeout.unref(); + + process.addListener('message', messageHandler as () => void); + }); + }); + } +} + +export const ipcClient = BackstageIpcClient.create(); diff --git a/packages/backend-dev-utils/src/setupTests.ts b/packages/backend-dev-utils/src/setupTests.ts new file mode 100644 index 0000000000..4b9026cde5 --- /dev/null +++ b/packages/backend-dev-utils/src/setupTests.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2023 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/packages/backend-next/package.json b/packages/backend-next/package.json index cc537ef514..fb821e19eb 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -18,7 +18,7 @@ "backstage" ], "scripts": { - "start": "backstage-cli package start", + "start": "EXPERIMENTAL_BACKEND_START=1 backstage-cli package start", "build": "backstage-cli package build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index a7700d81ac..8293ac193f 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -47,7 +47,7 @@ export interface BackendModuleRegistrationPoints { // @public export interface BackendPluginConfig { - id: string; + pluginId: string; // (undocumented) register(reg: BackendPluginRegistrationPoints): void; } @@ -275,7 +275,7 @@ export type ExtensionPoint = { id: string; T: T; toString(): string; - $$ref: 'extension-point'; + $$type: '@backstage/ExtensionPoint'; }; // @public @@ -294,14 +294,19 @@ export interface IdentityService extends IdentityApi {} // @public (undocumented) export interface LifecycleService { - addShutdownHook(options: LifecycleServiceShutdownHook): void; + addShutdownHook( + hook: LifecycleServiceShutdownHook, + options?: LifecycleServiceShutdownOptions, + ): void; } // @public (undocumented) -export type LifecycleServiceShutdownHook = { - fn: () => void | Promise; +export type LifecycleServiceShutdownHook = () => void | Promise; + +// @public (undocumented) +export interface LifecycleServiceShutdownOptions { logger?: LoggerService; -}; +} // @public export interface LoggerService { @@ -490,7 +495,7 @@ export type ServiceRef< scope: TScope; T: TService; toString(): string; - $$ref: 'service'; + $$type: '@backstage/ServiceRef'; }; // @public (undocumented) @@ -508,7 +513,7 @@ export interface ServiceRefConfig { // @public export interface SharedBackendEnvironment { // (undocumented) - $$type: 'SharedBackendEnvironment'; + $$type: '@backstage/SharedBackendEnvironment'; } // @public diff --git a/packages/backend-plugin-api/src/services/definitions/LifecycleService.ts b/packages/backend-plugin-api/src/services/definitions/LifecycleService.ts index 59d14aa61d..89351194e6 100644 --- a/packages/backend-plugin-api/src/services/definitions/LifecycleService.ts +++ b/packages/backend-plugin-api/src/services/definitions/LifecycleService.ts @@ -19,14 +19,17 @@ import { LoggerService } from './LoggerService'; /** * @public */ -export type LifecycleServiceShutdownHook = { - fn: () => void | Promise; +export type LifecycleServiceShutdownHook = () => void | Promise; +/** + * @public + */ +export interface LifecycleServiceShutdownOptions { /** * Optional {@link LoggerService} that will be used for logging instead of the default logger. */ logger?: LoggerService; -}; +} /** * @public @@ -35,5 +38,8 @@ export interface LifecycleService { /** * Register a function to be called when the backend is shutting down. */ - addShutdownHook(options: LifecycleServiceShutdownHook): void; + addShutdownHook( + hook: LifecycleServiceShutdownHook, + options?: LifecycleServiceShutdownOptions, + ): void; } diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index c5af0967bd..720b4d634d 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -28,6 +28,7 @@ export type { HttpRouterService } from './HttpRouterService'; export type { LifecycleService, LifecycleServiceShutdownHook, + LifecycleServiceShutdownOptions, } from './LifecycleService'; export type { LoggerService, LogMeta } from './LoggerService'; export type { PermissionsService } from './PermissionsService'; diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index 2615c5e3a3..3e68e9349d 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -44,7 +44,7 @@ export type ServiceRef< toString(): string; - $$ref: 'service'; + $$type: '@backstage/ServiceRef'; }; /** @public */ @@ -119,7 +119,7 @@ export function createServiceRef( toString() { return `serviceRef{${config.id}}`; }, - $$ref: 'service', // TODO: declare + $$type: '@backstage/ServiceRef', __defaultFactory: defaultFactory, } as ServiceRef & { __defaultFactory?: ( diff --git a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts index ec2cf1fe49..381a4799d2 100644 --- a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts +++ b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts @@ -48,7 +48,7 @@ describe('createSharedEnvironment', () => { expect(env).toBeDefined(); const internalEnv = env() as unknown as InternalSharedBackendEnvironment; expect(internalEnv).toEqual({ - $$type: 'SharedBackendEnvironment', + $$type: '@backstage/SharedBackendEnvironment', version: 'v1', services: undefined, }); diff --git a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts index 2eb2cdec6a..57f0533523 100644 --- a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts +++ b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts @@ -31,7 +31,7 @@ export interface SharedBackendEnvironmentConfig { * @public */ export interface SharedBackendEnvironment { - $$type: 'SharedBackendEnvironment'; + $$type: '@backstage/SharedBackendEnvironment'; // NOTE: This type is opaque in order to allow for future API evolution without // cluttering the external API. For example we might want to add support @@ -93,7 +93,7 @@ export function createSharedEnvironment< // Here to ensure type safety in this internal implementation. const env: SharedBackendEnvironment & InternalSharedBackendEnvironment = { - $$type: 'SharedBackendEnvironment', + $$type: '@backstage/SharedBackendEnvironment', version: 'v1', services, }; diff --git a/packages/backend-plugin-api/src/wiring/factories.test.ts b/packages/backend-plugin-api/src/wiring/factories.test.ts index dbdf0ead14..8e1b30ea53 100644 --- a/packages/backend-plugin-api/src/wiring/factories.test.ts +++ b/packages/backend-plugin-api/src/wiring/factories.test.ts @@ -33,7 +33,7 @@ describe('createExtensionPoint', () => { describe('createBackendPlugin', () => { it('should create a BackendPlugin', () => { const plugin = createBackendPlugin((_options: { a: string }) => ({ - id: 'x', + pluginId: 'x', register() {}, })); expect(plugin).toBeDefined(); @@ -47,7 +47,7 @@ describe('createBackendPlugin', () => { it('should create plugins with optional options', () => { const plugin = createBackendPlugin((_options?: { a: string }) => ({ - id: 'x', + pluginId: 'x', register() {}, })); expect(plugin).toBeDefined(); @@ -59,7 +59,7 @@ describe('createBackendPlugin', () => { it('should create plugins without options', () => { const plugin = createBackendPlugin({ - id: 'x', + pluginId: 'x', register() {}, }); expect(plugin).toBeDefined(); @@ -74,7 +74,7 @@ describe('createBackendPlugin', () => { a: string; } const plugin = createBackendPlugin((_options: TestOptions) => ({ - id: 'x', + pluginId: 'x', register() {}, })); expect(plugin).toBeDefined(); @@ -91,7 +91,7 @@ describe('createBackendPlugin', () => { a: string; } const plugin = createBackendPlugin((_options?: TestOptions) => ({ - id: 'x', + pluginId: 'x', register() {}, })); expect(plugin).toBeDefined(); diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts index d281335a8e..26a65c292a 100644 --- a/packages/backend-plugin-api/src/wiring/factories.ts +++ b/packages/backend-plugin-api/src/wiring/factories.ts @@ -55,7 +55,7 @@ export function createExtensionPoint( toString() { return `extensionPoint{${config.id}}`; }, - $$ref: 'extension-point', // TODO: declare + $$type: '@backstage/ExtensionPoint', }; } @@ -72,7 +72,7 @@ export interface BackendPluginConfig { * * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} */ - id: string; + pluginId: string; register(reg: BackendPluginRegistrationPoints): void; } @@ -87,10 +87,18 @@ export function createBackendPlugin( config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig), ): (...params: TOptions) => BackendFeature { if (typeof config === 'function') { - return config; + return (...options: TOptions) => { + const c = config(...options); + return { + ...c, + id: c.pluginId, + }; + }; } - - return () => config; + return () => ({ + ...config, + id: config.pluginId, + }); } /** diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts index 6e081f57e5..ab8ce07284 100644 --- a/packages/backend-plugin-api/src/wiring/types.ts +++ b/packages/backend-plugin-api/src/wiring/types.ts @@ -32,7 +32,7 @@ export type ExtensionPoint = { toString(): string; - $$ref: 'extension-point'; + $$type: '@backstage/ExtensionPoint'; }; /** diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 232f7e8136..672c574cb2 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -6,7 +6,6 @@ import { Backend } from '@backstage/backend-app-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CacheService } from '@backstage/backend-plugin-api'; -import { ConfigReader } from '@backstage/config'; import { ConfigService } from '@backstage/backend-plugin-api'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { ExtendedHttpServer } from '@backstage/backend-app-api'; @@ -20,7 +19,6 @@ import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { RootLifecycleService } from '@backstage/backend-plugin-api'; -import { RootLoggerService } from '@backstage/backend-plugin-api'; import { SchedulerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; @@ -34,35 +32,27 @@ export function isDockerDisabledForTests(): boolean; export namespace mockServices { // (undocumented) export namespace cache { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } // (undocumented) - export function config(options?: config.Options): ConfigReader; + export function config(options?: config.Options): ConfigService; // (undocumented) export namespace config { // (undocumented) export type Options = { data?: JsonObject; }; - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: (options?: Options | undefined) => ServiceFactory; } // (undocumented) export namespace database { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } // (undocumented) export namespace httpRouter { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: ( options?: HttpRouterFactoryOptions | undefined, @@ -72,36 +62,26 @@ export namespace mockServices { export function identity(): IdentityService; // (undocumented) export namespace identity { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } // (undocumented) export namespace lifecycle { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } // (undocumented) export namespace logger { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } // (undocumented) export namespace permissions { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } // (undocumented) export namespace rootLifecycle { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } @@ -120,15 +100,11 @@ export namespace mockServices { debug: boolean; }; }; - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: (options?: Options | undefined) => ServiceFactory; } // (undocumented) export namespace scheduler { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } @@ -136,15 +112,11 @@ export namespace mockServices { export function tokenManager(): TokenManagerService; // (undocumented) export namespace tokenManager { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } // (undocumented) export namespace urlReader { - const // (undocumented) - ref: ServiceRef; const // (undocumented) factory: () => ServiceFactory; } diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index a01dbaf0fa..d1f0a86d43 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -37,7 +37,6 @@ "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index f86d8bac55..5f771f13c5 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -15,6 +15,7 @@ */ import { + ConfigService, coreServices, createServiceFactory, IdentityService, @@ -56,14 +57,13 @@ function simpleFactory( * @alpha */ export namespace mockServices { - export function config(options?: config.Options) { + export function config(options?: config.Options): ConfigService { return new ConfigReader(options?.data, 'mock-config'); } export namespace config { export type Options = { data?: JsonObject }; - export const ref = coreServices.config; - export const factory = simpleFactory(ref, config); + export const factory = simpleFactory(coreServices.config, config); } export function rootLogger(options?: rootLogger.Options): LoggerService { @@ -76,8 +76,7 @@ export namespace mockServices { | { error: boolean; warn: boolean; info: boolean; debug: boolean }; }; - export const ref = coreServices.rootLogger; - export const factory = simpleFactory(ref, rootLogger); + export const factory = simpleFactory(coreServices.rootLogger, rootLogger); } export function tokenManager(): TokenManagerService { @@ -93,55 +92,47 @@ export namespace mockServices { }; } export namespace tokenManager { - export const ref = coreServices.tokenManager; - export const factory = simpleFactory(ref, tokenManager); + export const factory = simpleFactory( + coreServices.tokenManager, + tokenManager, + ); } export function identity(): IdentityService { return new MockIdentityService(); } export namespace identity { - export const ref = coreServices.identity; - export const factory = simpleFactory(ref, identity); + export const factory = simpleFactory(coreServices.identity, identity); } // TODO(Rugvip): Not all core services have implementations available here yet. // some may need a bit more refactoring for it to be simpler to // re-implement functioning mock versions here. export namespace cache { - export const ref = coreServices.cache; export const factory = cacheFactory; } export namespace database { - export const ref = coreServices.database; export const factory = databaseFactory; } export namespace httpRouter { - export const ref = coreServices.httpRouter; export const factory = httpRouterFactory; } export namespace lifecycle { - export const ref = coreServices.lifecycle; export const factory = lifecycleFactory; } export namespace logger { - export const ref = coreServices.logger; export const factory = loggerFactory; } export namespace permissions { - export const ref = coreServices.permissions; export const factory = permissionsFactory; } export namespace rootLifecycle { - export const ref = coreServices.rootLifecycle; export const factory = rootLifecycleFactory; } export namespace scheduler { - export const ref = coreServices.scheduler; export const factory = schedulerFactory; } export namespace urlReader { - export const ref = coreServices.urlReader; export const factory = urlReaderFactory; } } diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts index dca463a9ae..aa93a7dae1 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -40,10 +40,8 @@ beforeAll(async () => { env.registerInit({ deps: { lifecycle: coreServices.lifecycle }, async init({ lifecycle }) { - lifecycle.addShutdownHook({ - fn() { - globalTestBackendHasBeenStopped = true; - }, + lifecycle.addShutdownHook(() => { + globalTestBackendHasBeenStopped = true; }); }, }); @@ -145,7 +143,7 @@ describe('TestBackend', () => { lifecycle: coreServices.lifecycle, }, async init({ lifecycle }) { - lifecycle.addShutdownHook({ fn: shutdownSpy }); + lifecycle.addShutdownHook(shutdownSpy); }, }); }, @@ -165,7 +163,7 @@ describe('TestBackend', () => { expect.assertions(2); const testPlugin = createBackendPlugin({ - id: 'test', + pluginId: 'test', register(env) { env.registerInit({ deps: { @@ -201,7 +199,7 @@ describe('TestBackend', () => { it('should allow making requests via supertest', async () => { const testPlugin = createBackendPlugin({ - id: 'test', + pluginId: 'test', register(env) { env.registerInit({ deps: { diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index db1016fcb6..27a8c5bceb 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -129,12 +129,7 @@ export async function startTestBackend< { logger }, ); - lifecycle.addShutdownHook({ - async fn() { - await server.stop(); - }, - logger, - }); + lifecycle.addShutdownHook(() => server.stop(), { logger }); await server.start(); diff --git a/packages/catalog-model/examples/all-apis.yaml b/packages/catalog-model/examples/all-apis.yaml index e23f1b3656..472897bfac 100644 --- a/packages/catalog-model/examples/all-apis.yaml +++ b/packages/catalog-model/examples/all-apis.yaml @@ -6,6 +6,7 @@ metadata: spec: targets: - ./apis/hello-world-api.yaml + - ./apis/hello-world-trpc-api.yaml - ./apis/petstore-api.yaml - ./apis/spotify-api.yaml - ./apis/streetlights-api.yaml diff --git a/packages/catalog-model/examples/apis/hello-world-trpc-api.yaml b/packages/catalog-model/examples/apis/hello-world-trpc-api.yaml new file mode 100644 index 0000000000..f5807537b3 --- /dev/null +++ b/packages/catalog-model/examples/apis/hello-world-trpc-api.yaml @@ -0,0 +1,25 @@ +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: hello-world-trpc + description: Hello World example for tRPC +spec: + type: trpc + lifecycle: experimental + owner: team-c + definition: | + import { z } from 'zod'; + import { publicProcedure, router } from '../trpc'; + + export const apiRouter = router({ + version: publicProcedure.query(() => { + return { version: '0.42.0' }; + }), + hello: publicProcedure + .input(z.object({ username: z.string().nullish() }).nullish()) + .query(({ input, ctx }) => { + return { + text: `hello ${input?.username ?? ctx.user?.name ?? 'world'}`, + }; + }), + }); diff --git a/packages/catalog-model/src/schema/kinds/API.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/API.v1alpha1.schema.json index fae1225764..3d5af710e0 100644 --- a/packages/catalog-model/src/schema/kinds/API.v1alpha1.schema.json +++ b/packages/catalog-model/src/schema/kinds/API.v1alpha1.schema.json @@ -46,7 +46,7 @@ "type": { "type": "string", "description": "The type of the API definition.", - "examples": ["openapi", "asyncapi", "graphql", "grpc"], + "examples": ["openapi", "asyncapi", "graphql", "grpc", "trpc"], "minLength": 1 }, "lifecycle": { diff --git a/packages/cli/asset-types/asset-types.d.ts b/packages/cli/asset-types/asset-types.d.ts index 0bd0922004..9bd158bb8e 100644 --- a/packages/cli/asset-types/asset-types.d.ts +++ b/packages/cli/asset-types/asset-types.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ /// /// diff --git a/packages/cli/config/eslint-factory.js b/packages/cli/config/eslint-factory.js index a40aa79fb3..dde853fdff 100644 --- a/packages/cli/config/eslint-factory.js +++ b/packages/cli/config/eslint-factory.js @@ -60,7 +60,7 @@ function createConfig(dir, extraConfig = {}) { '@spotify/eslint-config-typescript', 'prettier', 'plugin:jest/recommended', - 'plugin:monorepo/recommended', + 'plugin:@backstage/recommended', ...(extraExtends ?? []), ], parser: '@typescript-eslint/parser', @@ -88,32 +88,6 @@ function createConfig(dir, extraConfig = {}) { '@typescript-eslint/no-redeclare': 'error', 'no-undef': 'off', 'import/newline-after-import': 'error', - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: dir - ? [ - `!${joinPath(dir, 'src/**')}`, - joinPath(dir, 'src/**/*.test.*'), - joinPath(dir, 'src/**/*.stories.*'), - joinPath(dir, 'src/**/__testUtils__/**'), - joinPath(dir, 'src/**/__mocks__/**'), - joinPath(dir, 'src/setupTests.*'), - ] - : [ - // Legacy config for packages that don't provide a dir - '**/*.test.*', - '**/*.stories.*', - '**/__testUtils__/**', - '**/__mocks__/**', - '**/src/setupTests.*', - '**/dev/**', - ], - optionalDependencies: true, - peerDependencies: true, - bundledDependencies: true, - }, - ], 'no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'error', '@typescript-eslint/consistent-type-assertions': 'error', @@ -135,9 +109,6 @@ function createConfig(dir, extraConfig = {}) { ...(restrictedSrcImports ?? []), ], patterns: [ - // Avoid cross-package imports - '**/../../**/*/src/**', - '**/../../**/*/src', // Prevent imports of stories or tests '*.stories*', '*.test*', @@ -187,8 +158,6 @@ function createConfig(dir, extraConfig = {}) { ...(restrictedImports ?? []), ...(restrictedTestImports ?? []), ], - // Avoid cross-package imports - patterns: ['**/../../**/*/src/**', '**/../../**/*/src'], }, ], }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 909ac8460c..587c91ce5e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,8 +34,10 @@ "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/eslint-plugin": "workspace:^", "@backstage/release-manifests": "workspace:^", "@backstage/types": "workspace:^", + "@esbuild-kit/cjs-loader": "^2.4.1", "@manypkg/get-packages": "^1.1.3", "@octokit/request": "^6.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", @@ -65,6 +67,7 @@ "chalk": "^4.0.0", "chokidar": "^3.3.1", "commander": "^9.1.0", + "cross-spawn": "^7.0.3", "css-loader": "^6.5.1", "diff": "^5.0.0", "esbuild": "^0.17.0", @@ -76,7 +79,6 @@ "eslint-plugin-import": "^2.25.4", "eslint-plugin-jest": "^27.0.0", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-monorepo": "^0.3.2", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", "eslint-webpack-plugin": "^3.1.1", @@ -136,6 +138,7 @@ "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@backstage/theme": "workspace:^", + "@types/cross-spawn": "^6.0.2", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", "@types/fs-extra": "^9.0.1", diff --git a/packages/cli/src/commands/start/startBackend.ts b/packages/cli/src/commands/start/startBackend.ts index 61ada7a291..3e6a13be96 100644 --- a/packages/cli/src/commands/start/startBackend.ts +++ b/packages/cli/src/commands/start/startBackend.ts @@ -17,6 +17,7 @@ import fs from 'fs-extra'; import { paths } from '../../lib/paths'; import { serveBackend } from '../../lib/bundler'; +import { startBackendExperimental } from '../../lib/experimental/startBackendExperimental'; interface StartBackendOptions { checksEnabled: boolean; @@ -25,17 +26,28 @@ interface StartBackendOptions { } export async function startBackend(options: StartBackendOptions) { - // Cleaning dist/ before we start the dev process helps work around an issue - // where we end up with the entrypoint executing multiple times, causing - // a port bind conflict among other things. - await fs.remove(paths.resolveTarget('dist')); + if (process.env.EXPERIMENTAL_BACKEND_START) { + const waitForExit = await startBackendExperimental({ + entry: 'src/index', + checksEnabled: false, // not supported + inspectEnabled: options.inspectEnabled, + inspectBrkEnabled: options.inspectBrkEnabled, + }); - const waitForExit = await serveBackend({ - entry: 'src/index', - checksEnabled: options.checksEnabled, - inspectEnabled: options.inspectEnabled, - inspectBrkEnabled: options.inspectBrkEnabled, - }); + await waitForExit(); + } else { + // Cleaning dist/ before we start the dev process helps work around an issue + // where we end up with the entrypoint executing multiple times, causing + // a port bind conflict among other things. + await fs.remove(paths.resolveTarget('dist')); - await waitForExit(); + const waitForExit = await serveBackend({ + entry: 'src/index', + checksEnabled: options.checksEnabled, + inspectEnabled: options.inspectEnabled, + inspectBrkEnabled: options.inspectBrkEnabled, + }); + + await waitForExit(); + } } diff --git a/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts b/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts index 2533131a40..cf37c2a664 100644 --- a/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts +++ b/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts @@ -50,7 +50,7 @@ export async function buildTypeDefinitionsWorker( */ const { PackageJsonLookup, - // eslint-disable-next-line import/no-extraneous-dependencies + // eslint-disable-next-line @backstage/no-undeclared-imports } = require('@rushstack/node-core-library/lib/PackageJsonLookup'); const old = PackageJsonLookup.prototype.tryGetPackageJsonFilePathFor; diff --git a/packages/cli/src/lib/experimental/IpcServer.ts b/packages/cli/src/lib/experimental/IpcServer.ts new file mode 100644 index 0000000000..8ceb72b046 --- /dev/null +++ b/packages/cli/src/lib/experimental/IpcServer.ts @@ -0,0 +1,102 @@ +/* + * Copyright 2023 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 { serializeError } from '@backstage/errors'; +import { ChildProcess } from 'child_process'; + +interface RequestMeta { + generation: number; +} + +type MethodHandler = ( + req: TRequest, + meta: RequestMeta, +) => Promise; + +interface Request { + id: number; + method: string; + body: unknown; + type: string; +} + +const requestType = '@backstage/cli/channel/request'; +const responseType = '@backstage/cli/channel/response'; + +export class IpcServer { + #generation = 1; + #methods = new Map>(); + + addChild(child: ChildProcess) { + const generation = this.#generation++; + const sendMessage = child.send?.bind(child); + if (!sendMessage) { + return; + } + + const messageListener = (request: Request) => { + if (request.type !== requestType) { + return; + } + + const handler = this.#methods.get(request.method); + if (!handler) { + sendMessage({ + type: responseType, + id: request.id, + error: { + name: 'NotFoundError', + message: `No handler registered for method ${request.method}`, + }, + }); + return; + } + + Promise.resolve() + .then(() => handler(request.body, { generation })) + .then(response => + sendMessage({ + type: responseType, + id: request.id, + body: response, + }), + ) + .catch(error => + sendMessage({ + type: responseType, + id: request.id, + error: serializeError(error), + }), + ); + }; + + child.addListener('message', messageListener as (req: unknown) => void); + + child.addListener('exit', () => { + child.removeListener('message', messageListener); + }); + } + + registerMethod( + method: string, + handler: MethodHandler, + ) { + if (this.#methods.has(method)) { + throw new Error(`A handler is already registered for method ${method}`); + } + this.#methods.set(method, handler); + } +} diff --git a/packages/cli/src/lib/experimental/ServerDataStore.ts b/packages/cli/src/lib/experimental/ServerDataStore.ts new file mode 100644 index 0000000000..c0232157cb --- /dev/null +++ b/packages/cli/src/lib/experimental/ServerDataStore.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2023 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 { IpcServer } from './IpcServer'; + +interface StorageItem { + generation: number; + data: unknown; +} + +interface SaveRequest { + key: string; + data: unknown; +} + +interface SaveResponse { + saved: boolean; +} + +interface LoadRequest { + key: string; +} + +interface LoadResponse { + loaded: boolean; + data: unknown; +} + +export class ServerDataStore { + static bind(server: IpcServer): void { + const store = new Map(); + + server.registerMethod( + 'DevDataStore.save', + async (request, { generation }) => { + const { key, data } = request; + if (!key) { + throw new Error('Key is required in DevDataStore.save'); + } + + const item = store.get(key); + + if (!item) { + store.set(key, { generation, data }); + return { saved: true }; + } + + if (item.generation > generation) { + return { saved: false }; + } + + store.set(key, { generation, data }); + return { saved: true }; + }, + ); + + server.registerMethod( + 'DevDataStore.load', + async request => { + const item = store.get(request.key); + return { loaded: Boolean(item), data: item?.data }; + }, + ); + } +} diff --git a/packages/cli/src/lib/experimental/startBackendExperimental.ts b/packages/cli/src/lib/experimental/startBackendExperimental.ts new file mode 100644 index 0000000000..c08fbda0da --- /dev/null +++ b/packages/cli/src/lib/experimental/startBackendExperimental.ts @@ -0,0 +1,138 @@ +/* + * 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 { BackendServeOptions } from '../bundler/types'; +import type { ChildProcess } from 'child_process'; +import { fileURLToPath } from 'url'; +import { isAbsolute as isAbsolutePath } from 'path'; +import { FSWatcher, watch } from 'chokidar'; +import { IpcServer } from './IpcServer'; +import { ServerDataStore } from './ServerDataStore'; +import debounce from 'lodash/debounce'; +import spawn from 'cross-spawn'; +import { paths } from '../paths'; + +const loaderArgs = ['--require', require.resolve('@esbuild-kit/cjs-loader')]; + +export async function startBackendExperimental(options: BackendServeOptions) { + const envEnv = process.env as { NODE_ENV: string }; + if (!envEnv.NODE_ENV) { + envEnv.NODE_ENV = 'development'; + } + + // Set up the parent IPC server and bind the available services + const server = new IpcServer(); + ServerDataStore.bind(server); + + let exiting = false; + let child: ChildProcess | undefined; + let watcher: FSWatcher | undefined = undefined; + let shutdownPromise: Promise | undefined = undefined; + + const restart = debounce(async () => { + // If a re-trigger happens during an existing shutdown, we just ignore it + if (shutdownPromise) { + return; + } + + if (child && !child.killed && child.exitCode === null) { + // We always wait for the existing process to exit, to make sure we don't get IPC conflicts + shutdownPromise = new Promise(resolve => child!.once('exit', resolve)); + child.kill(); + await shutdownPromise; + shutdownPromise = undefined; + } + + // We've received a shutdown signal + if (exiting) { + return; + } + + const optionArgs = new Array(); + if (options.inspectEnabled) { + optionArgs.push('--inspect'); + } else if (options.inspectBrkEnabled) { + optionArgs.push('--inspect-brk'); + } + + const userArgs = process.argv + .slice(['node', 'backstage-cli', 'package', 'start'].length) + .filter(arg => !optionArgs.includes(arg)); + + child = spawn( + process.execPath, + [...loaderArgs, ...optionArgs, options.entry, ...userArgs], + { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env: { + ...process.env, + BACKSTAGE_CLI_CHANNEL: '1', + ESBK_TSCONFIG_PATH: paths.resolveTargetRoot('tsconfig.json'), + }, + serialization: 'advanced', + }, + ); + + server.addChild(child); + + // This captures messages sent by @esbuild-kit/cjs-loader + child.on('message', (data: { type?: string } | null) => { + if (typeof data === 'object' && data?.type === 'dependency') { + let path = (data as { path: string }).path; + if (path.startsWith('file:')) { + path = fileURLToPath(path); + } + + if (isAbsolutePath(path)) { + watcher?.add(path); + } + } + }); + }, 100); + + restart(); + + watcher = watch([paths.targetDir], { + cwd: process.cwd(), + ignored: ['**/.*/**', '**/node_modules/**'], + ignoreInitial: true, + ignorePermissionErrors: true, + }).on('all', restart); + + // Trigger restart on hitting enter in the terminal + process.stdin.on('data', restart); + + const exitPromise = new Promise(resolveExitPromise => { + async function handleSignal(signal: NodeJS.Signals) { + exiting = true; + + // Forward signals to child and wait for it to exit if still running + if (child && child.exitCode === null) { + await new Promise(resolve => { + child!.on('close', resolve); + child!.kill(signal); + }); + } + + resolveExitPromise(); + } + + process.once('SIGINT', handleSignal); + process.once('SIGTERM', handleSignal); + }); + + return () => exitPromise; +} diff --git a/packages/cli/src/lib/version.test.ts b/packages/cli/src/lib/version.test.ts index fb9bf83e7c..604129679b 100644 --- a/packages/cli/src/lib/version.test.ts +++ b/packages/cli/src/lib/version.test.ts @@ -17,7 +17,6 @@ import mockFs from 'mock-fs'; import { packageVersions, createPackageVersionProvider } from './version'; import { Lockfile } from './versioning'; -// eslint-disable-next-line monorepo/no-internal-import import corePluginApiPkg from '@backstage/core-plugin-api/package.json'; describe('createPackageVersionProvider', () => { diff --git a/packages/cli/src/lib/version.ts b/packages/cli/src/lib/version.ts index 5a199236f5..bf89842d5f 100644 --- a/packages/cli/src/lib/version.ts +++ b/packages/cli/src/lib/version.ts @@ -19,7 +19,7 @@ import semver from 'semver'; import { paths } from './paths'; import { Lockfile } from './versioning'; -/* eslint-disable monorepo/no-relative-import */ +/* eslint-disable @backstage/no-relative-monorepo-imports */ /* This is a list of all packages used by the templates. If dependencies are added or removed, diff --git a/packages/cli/src/types.d.ts b/packages/cli/src/types.d.ts index fedbe61f02..1222b2b4b8 100644 --- a/packages/cli/src/types.d.ts +++ b/packages/cli/src/types.d.ts @@ -258,3 +258,5 @@ declare module 'webpack-node-externals' { } } } + +declare module '@esbuild-kit/cjs-loader' {} diff --git a/packages/codemods/.eslintrc.js b/packages/codemods/.eslintrc.js index f9fb2e27c9..e2a53a6ad2 100644 --- a/packages/codemods/.eslintrc.js +++ b/packages/codemods/.eslintrc.js @@ -1,14 +1 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { - rules: { - 'no-console': 0, - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: true, - optionalDependencies: false, - peerDependencies: false, - bundledDependencies: false, - }, - ], - }, -}); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/codemods/package.json b/packages/codemods/package.json index cbcc0613d4..1ae4d04485 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -35,6 +35,7 @@ "dependencies": { "@backstage/cli-common": "workspace:^", "chalk": "^4.0.0", + "commander": "^9.1.0", "jscodeshift": "^0.14.0", "jscodeshift-add-imports": "^1.0.10" }, @@ -42,7 +43,6 @@ "@backstage/cli": "workspace:^", "@types/jscodeshift": "^0.11.0", "@types/node": "^16.11.26", - "commander": "^9.1.0", "ts-node": "^10.0.0" }, "nodemonConfig": { diff --git a/packages/core-components/src/components/LogViewer/RealLogViewer.test.tsx b/packages/core-components/src/components/LogViewer/RealLogViewer.test.tsx index dd707778e1..e1aac76381 100644 --- a/packages/core-components/src/components/LogViewer/RealLogViewer.test.tsx +++ b/packages/core-components/src/components/LogViewer/RealLogViewer.test.tsx @@ -18,7 +18,7 @@ import React, { ReactNode } from 'react'; import userEvent from '@testing-library/user-event'; import { renderInTestApp } from '@backstage/test-utils'; import { RealLogViewer } from './RealLogViewer'; -// eslint-disable-next-line import/no-extraneous-dependencies +// eslint-disable-next-line @backstage/no-undeclared-imports import copyToClipboard from 'copy-to-clipboard'; // Used by useCopyToClipboard diff --git a/packages/core-components/src/components/LogViewer/useLogViewerSelection.test.tsx b/packages/core-components/src/components/LogViewer/useLogViewerSelection.test.tsx index 7e00d50f33..93a6dfd7e9 100644 --- a/packages/core-components/src/components/LogViewer/useLogViewerSelection.test.tsx +++ b/packages/core-components/src/components/LogViewer/useLogViewerSelection.test.tsx @@ -20,7 +20,7 @@ import { TestApiProvider, MockErrorApi } from '@backstage/test-utils'; import { errorApiRef } from '@backstage/core-plugin-api'; import { AnsiLine } from './AnsiProcessor'; import { useLogViewerSelection } from './useLogViewerSelection'; -// eslint-disable-next-line import/no-extraneous-dependencies +// eslint-disable-next-line @backstage/no-undeclared-imports import copyToClipboard from 'copy-to-clipboard'; // Used by useCopyToClipboard diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index 95d474aae4..3d8d0f3bb0 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -188,12 +188,12 @@ export function InfoCard(props: Props): JSX.Element { variants.forEach(name => { calculatedStyle = { ...calculatedStyle, - ...VARIANT_STYLES.card[name as keyof typeof VARIANT_STYLES['card']], + ...VARIANT_STYLES.card[name as keyof (typeof VARIANT_STYLES)['card']], }; calculatedCardStyle = { ...calculatedCardStyle, ...VARIANT_STYLES.cardContent[ - name as keyof typeof VARIANT_STYLES['cardContent'] + name as keyof (typeof VARIANT_STYLES)['cardContent'] ], }; }); diff --git a/packages/core-plugin-api/src/app/types.ts b/packages/core-plugin-api/src/app/types.ts index 2a71eda1f4..f3e0547ca4 100644 --- a/packages/core-plugin-api/src/app/types.ts +++ b/packages/core-plugin-api/src/app/types.ts @@ -17,7 +17,7 @@ // This is a bit of a hack that we use to avoid having to redeclare these types // within this package or have an explicit dependency on core-app-api. // These types end up being inlined and duplicated into this package at build time. -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line @backstage/no-relative-monorepo-imports export type { BootErrorPageProps, SignInPageProps, diff --git a/packages/create-app/bin/backstage-create-app b/packages/create-app/bin/backstage-create-app index 60dfd4990a..4639ea96dc 100755 --- a/packages/create-app/bin/backstage-create-app +++ b/packages/create-app/bin/backstage-create-app @@ -25,7 +25,7 @@ if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) { require('..'); } else { // Only used for development, so should be a devDependency - // eslint-disable-next-line import/no-extraneous-dependencies + // eslint-disable-next-line @backstage/no-undeclared-imports require('ts-node').register({ transpileOnly: true, project: path.resolve(__dirname, '../../../tsconfig.json'), diff --git a/packages/create-app/src/index.ts b/packages/create-app/src/index.ts index 216fab6740..fa235e6175 100644 --- a/packages/create-app/src/index.ts +++ b/packages/create-app/src/index.ts @@ -22,6 +22,7 @@ import { program } from 'commander'; import { exitWithError } from './lib/errors'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { version } from '../../../package.json'; import createApp from './createApp'; diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index b89b1d5463..72f5c2c9d5 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -/* eslint-disable monorepo/no-relative-import */ +/* eslint-disable @backstage/no-relative-monorepo-imports */ /* This is a list of all packages used by the template. If dependencies are added or removed, diff --git a/packages/create-app/templates/default-app/packages/app/cypress/.eslintrc.json b/packages/create-app/templates/default-app/packages/app/cypress/.eslintrc.json index 2b3a458b95..a467608916 100644 --- a/packages/create-app/templates/default-app/packages/app/cypress/.eslintrc.json +++ b/packages/create-app/templates/default-app/packages/app/cypress/.eslintrc.json @@ -7,15 +7,6 @@ { "assertFunctionNames": ["expect", "cy.contains"] } - ], - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": true, - "optionalDependencies": true, - "peerDependencies": true, - "bundledDependencies": true - } ] } } diff --git a/packages/e2e-test/.eslintrc.js b/packages/e2e-test/.eslintrc.js index f9fb2e27c9..e2a53a6ad2 100644 --- a/packages/e2e-test/.eslintrc.js +++ b/packages/e2e-test/.eslintrc.js @@ -1,14 +1 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { - rules: { - 'no-console': 0, - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: true, - optionalDependencies: false, - peerDependencies: false, - bundledDependencies: false, - }, - ], - }, -}); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 374eaa6d18..ee447a1736 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -26,22 +26,24 @@ "clean": "backstage-cli package clean" }, "bin": "bin/e2e-test", - "devDependencies": { - "@backstage/cli": "workspace:^", + "dependencies": { "@backstage/cli-common": "workspace:^", "@backstage/errors": "workspace:^", - "@types/fs-extra": "^9.0.1", - "@types/node": "^16.11.26", - "@types/puppeteer": "^5.4.4", "chalk": "^4.0.0", "commander": "^9.1.0", "cross-fetch": "^3.1.5", "fs-extra": "10.1.0", "handlebars": "^4.7.3", - "nodemon": "^2.0.2", "pgtools": "^0.3.0", "puppeteer": "^17.0.0", - "tree-kill": "^1.2.2", + "tree-kill": "^1.2.2" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@types/fs-extra": "^9.0.1", + "@types/node": "^16.11.26", + "@types/puppeteer": "^5.4.4", + "nodemon": "^2.0.2", "ts-node": "^10.0.0" }, "nodemonConfig": { diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index 3eedb90b21..254198ebb4 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -159,7 +159,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { appendDeps(pkg); } - // eslint-disable-next-line import/no-extraneous-dependencies + // eslint-disable-next-line @backstage/no-forbidden-package-imports appendDeps(require('@backstage/create-app/package.json')); print(`Preparing workspace`); diff --git a/packages/eslint-plugin/.eslintrc.js b/packages/eslint-plugin/.eslintrc.js new file mode 100644 index 0000000000..6b9de4544e --- /dev/null +++ b/packages/eslint-plugin/.eslintrc.js @@ -0,0 +1,34 @@ +/* + * Copyright 2023 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. + */ + +// Custom lint config here since source is ES5 +module.exports = { + plugins: ['@backstage'], + env: { + node: true, + es2021: true, + }, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + lib: require('@backstage/cli/config/tsconfig.json').compilerOptions.lib, + }, + rules: { + '@backstage/no-undeclared-imports': ['error'], + 'no-unused-expressions': 'off', + }, +}; diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md new file mode 100644 index 0000000000..c311670603 --- /dev/null +++ b/packages/eslint-plugin/README.md @@ -0,0 +1,42 @@ +# @backstage/eslint-plugin + +A collection of ESLint rules useful to Backstage projects. + +## Usage + +This ESLint plugin is part of the default lint configuration provided by the [Backstage CLI](https://www.npmjs.com/package/@backstage/cli), so you generally do not need to install it manually. + +If you do wish to install this plugin manually, start by adding it as a development dependency to your project: + +```sh +yarn add --dev @backstage/eslint-plugin +``` + +Then add it to your ESLint configuration: + +```js +extends: [ + 'plugin:@backstage/recommended', +], +``` + +Alternatively, if you want to install in individual rules manually: + +```js +plugins: [ + '@backstage', +], +rules: { + '@backstage/no-forbidden-package-imports': 'error', +} +``` + +## Rules + +The following rules are provided by this plugin: + +| Rule | Description | +| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| [@backstage/no-forbidden-package-imports](./docs/rules/no-forbidden-package-imports.md) | Disallow internal monorepo imports from package subpaths that are not exported. | +| [@backstage/no-relative-monorepo-imports](./docs/rules/no-relative-monorepo-imports.md) | Forbid relative imports that reach outside of the package in a monorepo. | +| [@backstage/no-undeclared-imports](./docs/rules/no-undeclared-imports.md) | Forbid imports of external packages that have not been declared in the appropriate dependencies field in `package.json`. | diff --git a/packages/eslint-plugin/docs/rules/no-forbidden-package-imports.md b/packages/eslint-plugin/docs/rules/no-forbidden-package-imports.md new file mode 100644 index 0000000000..7a6ae11c37 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-forbidden-package-imports.md @@ -0,0 +1,52 @@ +# @backstage/no-forbidden-package-imports + +Disallow internal monorepo imports from package subpaths that are not exported. + +## Usage + +Add the rules as follows, it has no options: + +```js +"@backstage/no-forbidden-package-imports": ["error"] +``` + +## Rule Details + +Given the following two target packages: + +```json +{ + "name": "@backstage/plugin-foo", + "files": ["dist", "type-utils"] +} +``` + +```json +{ + "name": "@backstage/plugin-bar", + "exports": { + ".": "./src/index.ts", + "./testUtils": "./src/testUtils/index.ts", + "./package.json": "./package.json" + } +} +``` + +### Fail + +```ts +import { FooCard } from '@backstage/plugin-foo/src/components'; +import { BarCard } from '@backstage/plugin-bar/src/components'; +``` + +### Pass + +```ts +import { FooCard } from '@backstage/plugin-foo'; +import { FooType } from '@backstage/plugin-foo/type-utils'; +import fooPkg from '@backstage/plugin-foo/package.json'; + +import { BarCard } from '@backstage/plugin-bar'; +import { renderBarCardExtension } from '@backstage/plugin-bar/testUtils'; +import barPkg from '@backstage/plugin-bar/package.json'; +``` diff --git a/packages/eslint-plugin/docs/rules/no-relative-monorepo-imports.md b/packages/eslint-plugin/docs/rules/no-relative-monorepo-imports.md new file mode 100644 index 0000000000..6a41a73fb6 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-relative-monorepo-imports.md @@ -0,0 +1,43 @@ +# @backstage/no-relative-monorepo-imports + +Forbid relative imports that reach outside of the package in a monorepo. + +## Usage + +Add the rules as follows, it has no options: + +```js +"@backstage/no-relative-monorepo-imports": ["error"] +``` + +The following patterns are considered files used during development, and only need dependencies to be declared in devDependencies: + +```python +!src/** # Any files outside of src are considered dev files +src/**/*.test.* +src/**/*.stories.* +src/**/__testUtils__/** +src/**/__mocks__/** +src/setupTests.* +``` + +## Rule Details + +Assuming an import from for example `plugins/bar/src/index.ts`: + +### Fail + +```ts +import { FooCard } from '../../foo'; + +import { FooCard } from '../../foo/src/components/FooCard'; +``` + +### Pass + +```ts +import { FooCard } from '@internal/plugin-foo'; + +// This is allowed by this rule, but not by no-forbidden-package-imports +import { FooCard } from '@internal/plugin-foo/src/components/FooCard'; +``` diff --git a/packages/eslint-plugin/docs/rules/no-undeclared-imports.md b/packages/eslint-plugin/docs/rules/no-undeclared-imports.md new file mode 100644 index 0000000000..6102c772e6 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-undeclared-imports.md @@ -0,0 +1,84 @@ +# @backstage/no-undeclared-imports + +Forbid imports of external packages that have not been declared in the appropriate dependencies field in `package.json`. + +## Usage + +Add the rules as follows, it has no options: + +```js +"@backstage/no-undeclared-imports": ["error"] +``` + +The following patterns are considered files used during development, and only need dependencies to be declared in devDependencies: + +```python +!src/** # Any files outside of src are considered dev files +src/**/*.test.* +src/**/*.stories.* +src/**/__testUtils__/** +src/**/__mocks__/** +src/setupTests.* +``` + +## Rule Details + +Given the following `package.json`: + +```json +{ + "name": "@backstage/plugin-foo", + "backstage": { + "role": "frontend-plugin" + }, + "dependencies": { + "react": "^17.0.0" + }, + "devDependencies": { + "@backstage/core-plugin-api": "^1.0.0" + }, + "peerDependencies": { + "@backstage/config": "*" + } +} +``` + +### Fail + +Inside `src/my-plugin.ts`: + +```ts +// Should be declared as a dependency +const _ = require('lodash'); +import _ from 'lodash'; + +// React should be a peer dependency in frontend plugins +import react from 'react'; + +// Should be declared as a dependency, not a dev dependency +import { useApi } from '@backstage/core-plugin-api'; +``` + +Inside `src/my-plugin.test.ts` (a test file): + +```ts +// Should be declared as a dev dependency +const _ = require('lodash'); +import _ from 'lodash'; +``` + +### Pass + +Inside `src/my-plugin.ts`: + +```ts +// Declared in peerDependencies, so it is allowed +import { ConfigReader } from '@backstage/config'; +``` + +Inside `src/my-plugin.test.ts` (a test file): + +```ts +// Declared as a dev dependency inside a test file +import { useApi } from '@backstage/core-plugin-api'; +``` diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js new file mode 100644 index 0000000000..20a0ff2d18 --- /dev/null +++ b/packages/eslint-plugin/index.js @@ -0,0 +1,33 @@ +/* + * Copyright 2023 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. + */ + +module.exports = { + configs: { + recommended: { + plugins: ['@backstage'], + rules: { + '@backstage/no-forbidden-package-imports': 'error', + '@backstage/no-relative-monorepo-imports': 'error', + '@backstage/no-undeclared-imports': 'error', + }, + }, + }, + rules: { + 'no-forbidden-package-imports': require('./rules/no-forbidden-package-imports'), + 'no-relative-monorepo-imports': require('./rules/no-relative-monorepo-imports'), + 'no-undeclared-imports': require('./rules/no-undeclared-imports'), + }, +}; diff --git a/packages/eslint-plugin/lib/getPackages.js b/packages/eslint-plugin/lib/getPackages.js new file mode 100644 index 0000000000..f5f2c63cc1 --- /dev/null +++ b/packages/eslint-plugin/lib/getPackages.js @@ -0,0 +1,71 @@ +/* + * Copyright 2023 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 + +const path = require('path'); +const manypkg = require('@manypkg/get-packages'); + +/** + * @typedef ExtendedPackage + * @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record, files?: Array }}} packageJson + */ + +/** + * @typedef PackageMap + * @type object + * + * @property {ExtendedPackage} root + * @property {ExtendedPackage[]} list + * @property {Map} map + * @property {(path: string) => ExtendedPackage | undefined} byPath + */ + +// Loads all packages in the monorepo once, and caches the result +module.exports = (function () { + /** @type {PackageMap | undefined} */ + let result = undefined; + /** @type {number} */ + let lastLoadAt = 0; + + /** @returns {PackageMap | undefined} */ + return function getPackages(/** @type {string} */ dir) { + if (result) { + // Only cache for 5 seconds, to avoid the need to reload ESLint servers + if (Date.now() - lastLoadAt > 5000) { + result = undefined; + } else { + return result; + } + } + const packages = manypkg.getPackagesSync(dir); + if (!packages) { + return undefined; + } + result = { + map: new Map(packages.packages.map(pkg => [pkg.packageJson.name, pkg])), + list: packages.packages, + root: packages.root, + byPath(filePath) { + return packages.packages.find( + pkg => !path.relative(pkg.dir, filePath).startsWith('..'), + ); + }, + }; + lastLoadAt = Date.now(); + return result; + }; +})(); diff --git a/packages/eslint-plugin/lib/visitImports.js b/packages/eslint-plugin/lib/visitImports.js new file mode 100644 index 0000000000..43401bf5f9 --- /dev/null +++ b/packages/eslint-plugin/lib/visitImports.js @@ -0,0 +1,173 @@ +/* + * Copyright 2023 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 + +const { builtinModules } = require('module'); +const getPackages = require('./getPackages'); + +/** + * @typedef LocalImport + * @type {object} + * @property {'local'} type + * @property {'value' | 'type'} kind + * @property {string} path + */ + +/** + * @typedef InternalImport + * @type {object} + * @property {'internal'} type + * @property {'value' | 'type'} kind + * @property {string} path + * @property {import('./getPackages').ExtendedPackage} package + */ + +/** + * @typedef ExternalImport + * @type {object} + * @property {'external'} type + * @property {'value' | 'type'} kind + * @property {string} path + * @property {string} packageName + */ + +/** + * @typedef BuiltinImport + * @type {object} + * @property {'builtin'} type + * @property {'value' | 'type'} kind + * @property {string} path + * @property {string} packageName + */ + +/** + * @callback ImportVisitor + * @param {ConsideredNode} node + * @param {LocalImport | InternalImport | ExternalImport | BuiltinImport} import + */ + +/** + * @typedef ConsideredNode + * @type {import('estree').ImportDeclaration | import('estree').ExportAllDeclaration | import('estree').ExportNamedDeclaration | import('estree').ImportExpression | import('estree').SimpleCallExpression} + */ + +/** + * @param {ConsideredNode} node + * @returns {undefined | {path: string, kind: 'type' | 'value'}} + */ +function getImportInfo(node) { + /** @type {import('estree').Expression | import('estree').SpreadElement | undefined | null} */ + let pathNode; + + if (node.type === 'CallExpression') { + if ( + node.callee.type === 'Identifier' && + node.callee.name == 'require' && + node.arguments.length === 1 + ) { + pathNode = node.arguments[0]; + } + } else { + pathNode = node.source; + } + + if (pathNode?.type !== 'Literal') { + return undefined; + } + if (typeof pathNode.value !== 'string') { + return undefined; + } + + /** @type {any} */ + const anyNode = node; + return { path: pathNode.value, kind: anyNode.importKind ?? 'value' }; +} + +/** + * @param visitor - Visitor callback + * @param {import('eslint').Rule.RuleContext} context + * @param {ImportVisitor} visitor + */ +module.exports = function visitImports(context, visitor) { + const packages = getPackages(context.getCwd()); + if (!packages) { + return; + } + + /** + * @param {ConsideredNode} node + */ + function visit(node) { + const info = getImportInfo(node); + if (!info) { + return; + } + + if (info.path[0] === '.') { + return visitor(node, { type: 'local', ...info }); + } + + const pathParts = info.path.split('/'); + + // Check for match with plain name, then namespaced name + let packageName; + let subPath; + if (info.path[0] === '@') { + packageName = pathParts.slice(0, 2).join('/'); + subPath = pathParts.slice(2).join('/'); + } else { + packageName = pathParts[0]; + subPath = pathParts.slice(1).join('/'); + } + const pkg = packages?.map.get(packageName); + if (!pkg) { + if ( + packageName.startsWith('node:') || + builtinModules.includes(packageName) + ) { + return visitor(node, { + type: 'builtin', + kind: info.kind, + path: subPath, + packageName, + }); + } + + return visitor(node, { + type: 'external', + kind: info.kind, + path: subPath, + packageName, + }); + } + + return visitor(node, { + type: 'internal', + kind: info.kind, + path: subPath, + package: pkg, + }); + } + + return { + ImportDeclaration: visit, + ExportAllDeclaration: visit, + ExportNamedDeclaration: visit, + ImportExpression: visit, + CallExpression: visit, + }; +}; diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json new file mode 100644 index 0000000000..84fc16b98f --- /dev/null +++ b/packages/eslint-plugin/package.json @@ -0,0 +1,28 @@ +{ + "name": "@backstage/eslint-plugin", + "description": "Backstage ESLint plugin", + "version": "0.0.0", + "publishConfig": { + "access": "public" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/eslint-plugin" + }, + "license": "Apache-2.0", + "main": "./index.js", + "scripts": { + "lint": "backstage-cli package lint", + "test": "backstage-cli package test" + }, + "dependencies": { + "@manypkg/get-packages": "^1.1.3", + "minimatch": "^5.1.2" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "eslint": "^8.33.0" + } +} diff --git a/packages/eslint-plugin/rules/no-forbidden-package-imports.js b/packages/eslint-plugin/rules/no-forbidden-package-imports.js new file mode 100644 index 0000000000..e7c1acca27 --- /dev/null +++ b/packages/eslint-plugin/rules/no-forbidden-package-imports.js @@ -0,0 +1,74 @@ +/* + * Copyright 2023 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 + +const visitImports = require('../lib/visitImports'); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + messages: { + forbidden: '{{packageName}} does not export {{subPath}}', + }, + docs: { + description: + 'Disallow internal monorepo imports from package subpaths that are not exported.', + url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-forbidden-package-imports.md', + }, + }, + create(context) { + return visitImports(context, (node, imp) => { + if (imp.type !== 'internal') { + return; + } + // Empty subpaths are always allowed + if (!imp.path) { + return; + } + + // If the import is listed in the package.json exports field, we allow it + const exp = imp.package.packageJson.exports; + if (exp && (exp[imp.path] || exp['./' + imp.path])) { + return; + } + if (!exp) { + // If there's no exports field, we allow anything listed in files, except dist + const files = imp.package.packageJson.files; + if ( + !files || + files.some(f => !f.startsWith('dist') && imp.path.startsWith(f)) + ) { + return; + } + // And also package.json + if (imp.path === 'package.json') { + return; + } + } + + context.report({ + node: node, + messageId: 'forbidden', + data: { + packageName: imp.package.packageJson.name || imp.package.dir, + subPath: imp.path, + }, + }); + }); + }, +}; diff --git a/packages/eslint-plugin/rules/no-relative-monorepo-imports.js b/packages/eslint-plugin/rules/no-relative-monorepo-imports.js new file mode 100644 index 0000000000..df30d35f67 --- /dev/null +++ b/packages/eslint-plugin/rules/no-relative-monorepo-imports.js @@ -0,0 +1,85 @@ +/* + * Copyright 2023 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 + +const path = require('path'); +const visitImports = require('../lib/visitImports'); +const getPackageMap = require('../lib/getPackages'); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + messages: { + outside: 'Import of {{path}} is outside of any known monorepo package', + forbidden: + "Relative imports of monorepo packages are forbidden, use '{{newImport}}' instead", + }, + docs: { + description: + 'Forbid relative imports that reach outside of the package in a monorepo.', + url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-relative-monorepo-imports.md', + }, + }, + create(context) { + const packages = getPackageMap(context.getCwd()); + if (!packages) { + return {}; + } + const filePath = context.getPhysicalFilename + ? context.getPhysicalFilename() + : context.getFilename(); + + const localPkg = packages.byPath(filePath); + if (!localPkg) { + return {}; + } + + return visitImports(context, (node, imp) => { + if (imp.type !== 'local') { + return; + } + + const target = path.resolve(path.dirname(filePath), imp.path); + if (!path.relative(localPkg.dir, target).startsWith('..')) { + return; + } + + const targetPkg = packages.byPath(target); + if (!targetPkg) { + context.report({ + node: node, + messageId: 'outside', + data: { + path: target, + }, + }); + return; + } + + const targetPath = path.relative(targetPkg.dir, target); + const targetName = targetPkg.packageJson.name ?? ''; + context.report({ + node: node, + messageId: 'forbidden', + data: { + newImport: targetPath ? `${targetName}/${targetPath}` : targetName, + }, + }); + }); + }, +}; diff --git a/packages/eslint-plugin/rules/no-undeclared-imports.js b/packages/eslint-plugin/rules/no-undeclared-imports.js new file mode 100644 index 0000000000..42624beb5c --- /dev/null +++ b/packages/eslint-plugin/rules/no-undeclared-imports.js @@ -0,0 +1,205 @@ +/* + * Copyright 2023 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 + +const path = require('path'); +const getPackageMap = require('../lib/getPackages'); +const visitImports = require('../lib/visitImports'); +const minimatch = require('minimatch'); + +const depFields = { + dep: 'dependencies', + dev: 'devDependencies', + peer: 'peerDependencies', +}; + +const devModulePatterns = [ + new minimatch.Minimatch('!src/**'), + new minimatch.Minimatch('src/**/*.test.*'), + new minimatch.Minimatch('src/**/*.stories.*'), + new minimatch.Minimatch('src/**/__testUtils__/**'), + new minimatch.Minimatch('src/**/__mocks__/**'), + new minimatch.Minimatch('src/setupTests.*'), +]; + +function getExpectedDepType( + /** @type {any} */ localPkg, + /** @type {string} */ impPath, + /** @type {string} */ modulePath, +) { + const role = localPkg?.backstage?.role; + // Some package roles have known dependency types + switch (role) { + case 'common-library': + case 'web-library': + case 'frontend-plugin': + case 'frontend-plugin-module': + case 'node-library': + case 'backend-plugin': + case 'backend-plugin-module': + switch (impPath) { + case 'react': + case 'react-dom': + case 'react-router': + case 'react-router-dom': + return 'peer'; + } + break; + case 'cli': + case 'frontend': + case 'backend': + default: + break; + } + + for (const pattern of devModulePatterns) { + if (pattern.match(modulePath)) { + return 'dev'; + } + } + return 'dep'; +} + +/** + * + * @param {import('@manypkg/get-packages').Package['packageJson']} pkg + * @param {string} name + * @param {ReturnType} expectedType + * @returns {{oldDepsField?: string, depsField: string} | undefined} + */ +function findConflict(pkg, name, expectedType) { + const isDep = pkg.dependencies?.[name]; + const isDevDep = pkg.devDependencies?.[name]; + const isPeerDep = pkg.peerDependencies?.[name]; + const depsField = depFields[expectedType]; + + if (expectedType === 'dep' && !isDep && !isPeerDep) { + const oldDepsField = isDevDep ? depFields.dev : undefined; + return { oldDepsField, depsField }; + } else if (expectedType === 'dev' && !isDevDep && !isDep && !isPeerDep) { + return { oldDepsField: undefined, depsField }; + } else if (expectedType === 'peer' && !isPeerDep) { + const oldDepsField = isDep + ? depFields.dep + : isDevDep + ? depFields.dev + : undefined; + + return { oldDepsField, depsField }; + } + return undefined; +} + +/** + * @param {string} depsField + */ +function getAddFlagForDepsField(depsField) { + switch (depsField) { + case depFields.dep: + return ''; + case depFields.dev: + return ' --dev'; + case depFields.peer: + return ' --peer'; + default: + return ''; + } +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + messages: { + undeclared: + "{{ packageName }} must be declared in {{ depsField }} of {{ packageJsonPath }}, run 'yarn --cwd {{ packagePath }} add{{ addFlag }} {{ packageName }}' from the project root.", + switch: + '{{ packageName }} is declared in {{ oldDepsField }}, but should be moved to {{ depsField }} in {{ packageJsonPath }}.', + }, + docs: { + description: + 'Forbid imports of external packages that have not been declared in the appropriate dependencies field in `package.json`.', + url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-undeclared-imports.md', + }, + }, + create(context) { + const packages = getPackageMap(context.getCwd()); + if (!packages) { + return {}; + } + const filePath = context.getPhysicalFilename + ? context.getPhysicalFilename() + : context.getFilename(); + + const localPkg = packages.byPath(filePath); + if (!localPkg) { + return {}; + } + + return visitImports(context, (node, imp) => { + if (imp.type !== 'external') { + return; + } + // We leave checking of type imports to the repo-tools check + if (imp.kind === 'type') { + return; + } + + const modulePath = path.relative(localPkg.dir, filePath); + const expectedType = getExpectedDepType( + localPkg.packageJson, + imp.packageName, + modulePath, + ); + + const conflict = findConflict( + localPkg.packageJson, + imp.packageName, + expectedType, + ); + + if (conflict) { + try { + const fullImport = imp.path + ? `${imp.packageName}/${imp.path}` + : imp.packageName; + require.resolve(fullImport, { + paths: [localPkg.dir], + }); + } catch { + // If the dependency doesn't resolve then it's likely a type import, ignore + return; + } + + const packagePath = path.relative(packages.root.dir, localPkg.dir); + const packageJsonPath = path.join(packagePath, 'package.json'); + + context.report({ + node, + messageId: conflict.oldDepsField ? 'switch' : 'undeclared', + data: { + ...conflict, + packagePath, + addFlag: getAddFlagForDepsField(conflict.depsField), + packageName: imp.packageName, + packageJsonPath: packageJsonPath, + }, + }); + } + }); + }, +}; diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/package.json new file mode 100644 index 0000000000..c35178c46f --- /dev/null +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/package.json @@ -0,0 +1,6 @@ +{ + "name": "root", + "workspaces": [ + "packages/*" + ] +} diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json new file mode 100644 index 0000000000..efef27047b --- /dev/null +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json @@ -0,0 +1,17 @@ +{ + "name": "@internal/bar", + "exports": { + ".": "./src/index.ts", + "./BarPage": "./src/components/Bar.tsx", + "./package.json": "./package.json" + }, + "backstage": { + "role": "frontend-plugin" + }, + "dependencies": { + "react-router": "*" + }, + "devDependencies": { + "react-router-dom": "*" + } +} diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json new file mode 100644 index 0000000000..563ec35120 --- /dev/null +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json @@ -0,0 +1,16 @@ +{ + "name": "@internal/foo", + "dependencies": { + "@internal/bar": "1.0.0" + }, + "devDependencies": { + "lodash": "*" + }, + "peerDependencies": { + "react": "*" + }, + "files": [ + "dist", + "type-utils" + ] +} diff --git a/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts b/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts new file mode 100644 index 0000000000..349b71319f --- /dev/null +++ b/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts @@ -0,0 +1,120 @@ +/* + * Copyright 2023 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 { RuleTester } from 'eslint'; +import path from 'path'; +import rule from '../rules/no-forbidden-package-imports'; + +const RULE = 'no-forbidden-package-imports'; +const FIXTURE = path.resolve(__dirname, '__fixtures__/monorepo'); + +const ERR = (name: string, path: string) => ({ + message: `${name} does not export ${path}`, +}); + +process.chdir(FIXTURE); + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + }, +}); + +ruleTester.run(RULE, rule, { + valid: [ + { + code: `import '@internal/foo'`, + filename: path.join(FIXTURE, 'index.ts'), + }, + { + code: `import '@internal/foo/type-utils'`, + filename: path.join(FIXTURE, 'index.ts'), + }, + { + code: `import '@internal/foo/type-utils/anything'`, + filename: path.join(FIXTURE, 'index.ts'), + }, + { + code: `import '@internal/foo/package.json'`, + filename: path.join(FIXTURE, 'index.ts'), + }, + { + code: `import '@internal/bar'`, + filename: path.join(FIXTURE, 'index.ts'), + }, + { + code: `import '@internal/bar/package.json'`, + filename: path.join(FIXTURE, 'index.ts'), + }, + { + code: `import '@internal/bar/BarPage'`, + filename: path.join(FIXTURE, 'index.ts'), + }, + ], + invalid: [ + { + code: `import '@internal/foo/FooPage'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/foo', 'FooPage')], + }, + { + code: `import '@internal/foo/dist'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/foo', 'dist')], + }, + { + code: `import '@internal/foo/dist/FooPage'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/foo', 'dist/FooPage')], + }, + { + code: `import '@internal/foo/src/FooPage'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/foo', 'src/FooPage')], + }, + { + code: `import '@internal/foo/src'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/foo', 'src')], + }, + { + code: `import '@internal/bar/OtherBarPage'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/bar', 'OtherBarPage')], + }, + { + code: `import '@internal/bar/dist'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/bar', 'dist')], + }, + { + code: `import '@internal/bar/dist/BarPage'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/bar', 'dist/BarPage')], + }, + { + code: `import '@internal/bar/src/BarPage'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/bar', 'src/BarPage')], + }, + { + code: `import '@internal/bar/src'`, + filename: path.join(FIXTURE, 'index.ts'), + errors: [ERR('@internal/bar', 'src')], + }, + ], +}); diff --git a/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts b/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts new file mode 100644 index 0000000000..6888d258cc --- /dev/null +++ b/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2023 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 { RuleTester } from 'eslint'; +import path from 'path'; +import rule from '../rules/no-relative-monorepo-imports'; + +const RULE = 'no-relative-monorepo-imports'; +const FIXTURE = path.resolve(__dirname, '__fixtures__/monorepo'); + +const ERR_OUTSIDE = (path: string) => ({ + message: `Import of ${path} is outside of any known monorepo package`, +}); +const ERR_FORBIDDEN = (newImp: string) => ({ + message: `Relative imports of monorepo packages are forbidden, use '${newImp}' instead`, +}); + +process.chdir(FIXTURE); + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + }, +}); + +ruleTester.run(RULE, rule, { + valid: [ + { + code: `import { version } from '@internal/foo'`, + filename: path.join(FIXTURE, 'packages/bar/src/index.ts'), + }, + { + code: `import { version } from '@internal/foo/src'`, + filename: path.join(FIXTURE, 'packages/bar/src/index.ts'), + }, + ], + invalid: [ + { + code: `import { version } from '../../foo'`, + filename: path.join(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ERR_FORBIDDEN('@internal/foo')], + }, + { + code: `import { version } from '../../foo/src'`, + filename: path.join(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ERR_FORBIDDEN('@internal/foo/src')], + }, + { + code: `import { version } from '../../../package.json'`, + filename: path.join(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ERR_OUTSIDE(path.join(FIXTURE, 'package.json'))], + }, + ], +}); diff --git a/packages/eslint-plugin/src/no-undeclared-imports.test.ts b/packages/eslint-plugin/src/no-undeclared-imports.test.ts new file mode 100644 index 0000000000..1b5cbee853 --- /dev/null +++ b/packages/eslint-plugin/src/no-undeclared-imports.test.ts @@ -0,0 +1,240 @@ +/* + * Copyright 2023 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 { RuleTester } from 'eslint'; +import { join as joinPath } from 'path'; +import rule from '../rules/no-undeclared-imports'; + +const RULE = 'no-undeclared-imports'; +const FIXTURE = joinPath(__dirname, '__fixtures__/monorepo'); + +const ERR_UNDECLARED = ( + name: string, + field: string, + path: string, + flag?: string, +) => ({ + message: `${name} must be declared in ${field} of ${joinPath( + path, + 'package.json', + )}, run 'yarn --cwd ${path} add${ + flag ? ` ${flag}` : '' + } ${name}' from the project root.`, +}); +const ERR_SWITCHED = ( + name: string, + old: string, + field: string, + path: string, +) => ({ + message: `${name} is declared in ${old}, but should be moved to ${field} in ${joinPath( + path, + 'package.json', + )}.`, +}); + +process.chdir(FIXTURE); + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + }, +}); + +ruleTester.run(RULE, rule, { + valid: [ + { + code: `import '@internal/foo'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.ts'), + }, + { + code: `import '@internal/bar'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.ts'), + }, + { + code: `import 'react'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.ts'), + }, + { + code: `import '@internal/foo'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.test.ts'), + }, + { + code: `import '@internal/bar'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.test.ts'), + }, + { + code: `import 'lodash'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.test.ts'), + }, + { + code: `import 'react'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.test.ts'), + }, + { + // We're only able to validate literals + code: `require('lod' + 'ash')`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + }, + ], + invalid: [ + { + code: `import 'lodash'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.ts'), + errors: [ + ERR_SWITCHED( + 'lodash', + 'devDependencies', + 'dependencies', + joinPath('packages', 'foo'), + ), + ], + }, + { + code: `import 'react-router'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_SWITCHED( + 'react-router', + 'dependencies', + 'peerDependencies', + joinPath('packages', 'bar'), + ), + ], + }, + { + code: `import 'react-router-dom'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_SWITCHED( + 'react-router-dom', + 'devDependencies', + 'peerDependencies', + joinPath('packages', 'bar'), + ), + ], + }, + { + code: `import 'lodash'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_UNDECLARED('lodash', 'dependencies', joinPath('packages', 'bar')), + ], + }, + { + code: `import { debounce } from 'lodash'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_UNDECLARED('lodash', 'dependencies', joinPath('packages', 'bar')), + ], + }, + { + code: `import * as _ from 'lodash'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_UNDECLARED('lodash', 'dependencies', joinPath('packages', 'bar')), + ], + }, + { + code: `import _ from 'lodash'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_UNDECLARED('lodash', 'dependencies', joinPath('packages', 'bar')), + ], + }, + { + code: `import('lodash')`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_UNDECLARED('lodash', 'dependencies', joinPath('packages', 'bar')), + ], + }, + { + code: `require('lodash')`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_UNDECLARED('lodash', 'dependencies', joinPath('packages', 'bar')), + ], + }, + { + code: `import 'lodash'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_UNDECLARED('lodash', 'dependencies', joinPath('packages', 'bar')), + ], + }, + { + code: `import 'lodash'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.test.ts'), + errors: [ + ERR_UNDECLARED( + 'lodash', + 'devDependencies', + joinPath('packages', 'bar'), + '--dev', + ), + ], + }, + { + code: `import 'react'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR_UNDECLARED( + 'react', + 'peerDependencies', + joinPath('packages', 'bar'), + '--peer', + ), + ], + }, + { + code: `import 'react'`, + filename: joinPath(FIXTURE, 'packages/bar/src/index.test.ts'), + errors: [ + ERR_UNDECLARED( + 'react', + 'peerDependencies', + joinPath('packages', 'bar'), + '--peer', + ), + ], + }, + { + code: `import 'react-dom'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.ts'), + errors: [ + ERR_UNDECLARED( + 'react-dom', + 'dependencies', + joinPath('packages', 'foo'), + ), + ], + }, + { + code: `import 'react-dom'`, + filename: joinPath(FIXTURE, 'packages/foo/src/index.test.ts'), + errors: [ + ERR_UNDECLARED( + 'react-dom', + 'devDependencies', + joinPath('packages', 'foo'), + '--dev', + ), + ], + }, + ], +}); diff --git a/packages/repo-tools/src/commands/api-reports/api-extractor.ts b/packages/repo-tools/src/commands/api-reports/api-extractor.ts index b037f40be6..39df70c21a 100644 --- a/packages/repo-tools/src/commands/api-reports/api-extractor.ts +++ b/packages/repo-tools/src/commands/api-reports/api-extractor.ts @@ -1107,7 +1107,7 @@ export async function categorizePackageDirs(packageDirs: any[]) { }); const role = pkgJson?.backstage?.role; if (!role) { - throw new Error(`No backstage.role in ${dir}/package.json`); + return; // Ignore packages without roles } if (role === 'cli') { cliPackageDirs.push(dir); diff --git a/packages/repo-tools/src/commands/type-deps/type-deps.ts b/packages/repo-tools/src/commands/type-deps/type-deps.ts index d4dd28aa14..4f8e5e5529 100644 --- a/packages/repo-tools/src/commands/type-deps/type-deps.ts +++ b/packages/repo-tools/src/commands/type-deps/type-deps.ts @@ -17,7 +17,7 @@ import fs from 'fs'; import { resolve as resolvePath } from 'path'; // Cba polluting root package.json, we'll have this -// eslint-disable-next-line import/no-extraneous-dependencies +// eslint-disable-next-line @backstage/no-undeclared-imports import chalk from 'chalk'; import { getPackages, Package } from '@manypkg/get-packages'; diff --git a/packages/techdocs-cli-embedded-app/cypress/.eslintrc.json b/packages/techdocs-cli-embedded-app/cypress/.eslintrc.json index 2b3a458b95..a467608916 100644 --- a/packages/techdocs-cli-embedded-app/cypress/.eslintrc.json +++ b/packages/techdocs-cli-embedded-app/cypress/.eslintrc.json @@ -7,15 +7,6 @@ { "assertFunctionNames": ["expect", "cy.contains"] } - ], - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": true, - "optionalDependencies": true, - "peerDependencies": true, - "bundledDependencies": true - } ] } } diff --git a/packages/techdocs-cli/cypress.config.js b/packages/techdocs-cli/cypress.config.js new file mode 100644 index 0000000000..abaa324d76 --- /dev/null +++ b/packages/techdocs-cli/cypress.config.js @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { defineConfig } from 'cypress'; +import { initPlugin } from '@frsource/cypress-plugin-visual-regression-diff/plugins'; + +module.exports = defineConfig({ + e2e: { + setupNodeEvents(on, config) { + initPlugin(on, config); + }, + + excludeSpecPattern: ['**/__snapshots__/*', '**/__image_snapshots__/*'], + }, + viewportWidth: 1920, + viewportHeight: 1080, + includeShadowDom: true, +}); diff --git a/packages/techdocs-cli/cypress.json b/packages/techdocs-cli/cypress.json deleted file mode 100644 index b6f3b96019..0000000000 --- a/packages/techdocs-cli/cypress.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "env": { - "mkDocsBaseUrl": "http://localhost:8000", - "backstageBaseUrl": "http://localhost:3000", - "cypress-plugin-snapshots": { - "autoCleanUp": false, - "imageConfig": { - "resizeDevicePixelRatio": true, - "threshold": 0.01 - } - } - }, - "viewportWidth": 1920, - "viewportHeight": 1080, - "includeShadowDom": true, - "ignoreTestFiles": ["**/__snapshots__/*", "**/__image_snapshots__/*"] -} diff --git a/packages/techdocs-cli/cypress/.eslintrc.json b/packages/techdocs-cli/cypress/.eslintrc.json index 2481ac5715..06965a82da 100644 --- a/packages/techdocs-cli/cypress/.eslintrc.json +++ b/packages/techdocs-cli/cypress/.eslintrc.json @@ -7,15 +7,6 @@ { "assertFunctionNames": ["expect", "cy.contains", "cy.document"] } - ], - "import/no-extraneous-dependencies": [ - "error", - { - "devDependencies": true, - "optionalDependencies": true, - "peerDependencies": true, - "bundledDependencies": true - } ] } } diff --git a/packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png b/packages/techdocs-cli/cypress/e2e/__image_snapshots__/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png similarity index 100% rename from packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png rename to packages/techdocs-cli/cypress/e2e/__image_snapshots__/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png diff --git a/packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - MkDocs Serve toMatchImageSnapshot - MkDocs Page #0.png b/packages/techdocs-cli/cypress/e2e/__image_snapshots__/TechDocs Live Preview - MkDocs Serve toMatchImageSnapshot - MkDocs Page #0.png similarity index 100% rename from packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - MkDocs Serve toMatchImageSnapshot - MkDocs Page #0.png rename to packages/techdocs-cli/cypress/e2e/__image_snapshots__/TechDocs Live Preview - MkDocs Serve toMatchImageSnapshot - MkDocs Page #0.png diff --git a/packages/techdocs-cli/cypress/integration/backstage_serve.js b/packages/techdocs-cli/cypress/e2e/backstage_serve.cy.js similarity index 94% rename from packages/techdocs-cli/cypress/integration/backstage_serve.js rename to packages/techdocs-cli/cypress/e2e/backstage_serve.cy.js index 15fc03f8f4..0267c8bfe6 100644 --- a/packages/techdocs-cli/cypress/integration/backstage_serve.js +++ b/packages/techdocs-cli/cypress/e2e/backstage_serve.cy.js @@ -48,11 +48,11 @@ describe('TechDocs Live Preview - Backstage Serve', () => { cy.get('.md-footer').should('have.length', 1); }); - it('toMatchImageSnapshot - Backstage TechDocs Page', () => { + it('matchImage - Backstage TechDocs Page', () => { cy.visit( `${Cypress.env('backstageBaseUrl')}/docs/default/component/local`, ).then(() => { - cy.document().toMatchImageSnapshot(); + cy.document().matchImage(); }); }); }); diff --git a/packages/techdocs-cli/cypress/integration/mkdocs_serve.js b/packages/techdocs-cli/cypress/e2e/mkdocs_serve.cy.js similarity index 94% rename from packages/techdocs-cli/cypress/integration/mkdocs_serve.js rename to packages/techdocs-cli/cypress/e2e/mkdocs_serve.cy.js index 1ace7298ce..d9da6caeca 100644 --- a/packages/techdocs-cli/cypress/integration/mkdocs_serve.js +++ b/packages/techdocs-cli/cypress/e2e/mkdocs_serve.cy.js @@ -40,9 +40,9 @@ describe('TechDocs Live Preview - MkDocs Serve', () => { cy.get('.md-footer').should('have.length', 1); }); - it('toMatchImageSnapshot - MkDocs Page', () => { + it('matchImage - MkDocs Page', () => { cy.visit(Cypress.env('mkDocsBaseUrl')).then(() => { - cy.document().toMatchImageSnapshot(); + cy.document().matchImage(); }); }); }); diff --git a/packages/techdocs-cli/cypress/plugins/index.js b/packages/techdocs-cli/cypress/plugins/index.js deleted file mode 100644 index 92faabb6ad..0000000000 --- a/packages/techdocs-cli/cypress/plugins/index.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -const { initPlugin } = require('cypress-plugin-snapshots/plugin'); - -/** - * @type {Cypress.PluginConfig} - */ -// eslint-disable-next-line no-unused-vars -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - initPlugin(on, config); - return config; -}; diff --git a/packages/techdocs-cli/cypress/support/index.js b/packages/techdocs-cli/cypress/support/e2e.js similarity index 94% rename from packages/techdocs-cli/cypress/support/index.js rename to packages/techdocs-cli/cypress/support/e2e.js index a1a5b10ea4..f06a08e9a5 100644 --- a/packages/techdocs-cli/cypress/support/index.js +++ b/packages/techdocs-cli/cypress/support/e2e.js @@ -30,7 +30,7 @@ // Import commands.js using ES2015 syntax: import './commands'; -import 'cypress-plugin-snapshots/commands'; +import '@frsource/cypress-plugin-visual-regression-diff/commands'; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 4e6bde27c2..5f2e7db65d 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -37,6 +37,7 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", + "@frsource/cypress-plugin-visual-regression-diff": "^3.2.8", "@types/commander": "^2.12.2", "@types/fs-extra": "^9.0.6", "@types/http-proxy": "^1.17.4", @@ -44,7 +45,6 @@ "@types/serve-handler": "^6.1.0", "@types/webpack-env": "^1.15.3", "cypress": "^10.0.0", - "cypress-plugin-snapshots": "^1.4.4", "find-process": "^1.4.5", "nodemon": "^2.0.2", "techdocs-cli-embedded-app": "link:../techdocs-cli-embedded-app", diff --git a/packages/techdocs-cli/src/lib/PublisherConfig.ts b/packages/techdocs-cli/src/lib/PublisherConfig.ts index 0d1c8fc8a9..0ef6b66eaf 100644 --- a/packages/techdocs-cli/src/lib/PublisherConfig.ts +++ b/packages/techdocs-cli/src/lib/PublisherConfig.ts @@ -17,7 +17,7 @@ import { ConfigReader } from '@backstage/config'; import { OptionValues } from 'commander'; -type Publisher = keyof typeof PublisherConfig['configFactories']; +type Publisher = keyof (typeof PublisherConfig)['configFactories']; type PublisherConfiguration = { [p in Publisher]?: any; } & { @@ -73,7 +73,7 @@ export class PublisherConfig { */ private static isKnownPublisher( type: string, - ): type is keyof typeof PublisherConfig['configFactories'] { + ): type is keyof (typeof PublisherConfig)['configFactories'] { return PublisherConfig.configFactories.hasOwnProperty(type); } diff --git a/plugins/adr/api-report.md b/plugins/adr/api-report.md index d6480cd090..4ced4e9416 100644 --- a/plugins/adr/api-report.md +++ b/plugins/adr/api-report.md @@ -11,6 +11,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { isAdrAvailable } from '@backstage/plugin-adr-common'; +import { ReactNode } from 'react'; import { ResultHighlight } from '@backstage/plugin-search-common'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -82,12 +83,18 @@ export type AdrReadResult = { }; // @public -export function AdrSearchResultListItem(props: { +export const AdrSearchResultListItem: ( + props: AdrSearchResultListItemProps, +) => JSX.Element | null; + +// @public (undocumented) +export type AdrSearchResultListItemProps = { lineClamp?: number; highlight?: ResultHighlight; + icon?: ReactNode; rank?: number; - result: AdrDocument; -}): JSX.Element; + result?: AdrDocument; +}; // @public export const EntityAdrContent: (props: { diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 9d915b7af7..5f3d3ba12a 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -39,6 +39,7 @@ "remark-gfm": "^3.0.1" }, "peerDependencies": { + "@types/react": "^16.13.1 || ^17.0.0", "react": "^16.13.1 || ^17.0.0", "react-router-dom": "6.0.0-beta.0 || ^6.3.0" }, diff --git a/plugins/adr/src/index.ts b/plugins/adr/src/index.ts index 2ec76b0fd6..f24b3ac531 100644 --- a/plugins/adr/src/index.ts +++ b/plugins/adr/src/index.ts @@ -30,5 +30,5 @@ export type { } from './api'; export { isAdrAvailable } from '@backstage/plugin-adr-common'; export * from './components/AdrReader'; -export { adrPlugin, EntityAdrContent } from './plugin'; -export * from './search'; +export { adrPlugin, AdrSearchResultListItem, EntityAdrContent } from './plugin'; +export type { AdrSearchResultListItemProps } from './search'; diff --git a/plugins/adr/src/plugin.ts b/plugins/adr/src/plugin.ts index f23df826cd..05e4adec4a 100644 --- a/plugins/adr/src/plugin.ts +++ b/plugins/adr/src/plugin.ts @@ -21,7 +21,9 @@ import { createRoutableExtension, discoveryApiRef, } from '@backstage/core-plugin-api'; +import { createSearchResultListItemExtension } from '@backstage/plugin-search-react'; import { rootRouteRef } from './routes'; +import { AdrSearchResultListItemProps } from './search'; /** * The Backstage plugin that holds ADR specific components @@ -57,3 +59,21 @@ export const EntityAdrContent = adrPlugin.provide( mountPoint: rootRouteRef, }), ); + +/** + * React extension used to render results on Search page or modal + * + * @public + */ +export const AdrSearchResultListItem: ( + props: AdrSearchResultListItemProps, +) => JSX.Element | null = adrPlugin.provide( + createSearchResultListItemExtension({ + name: 'AdrSearchResultListItem', + component: () => + import('./search/AdrSearchResultListItem').then( + m => m.AdrSearchResultListItem, + ), + predicate: result => result.type === 'adr', + }), +); diff --git a/plugins/adr/src/search/AdrSearchResultListItem.tsx b/plugins/adr/src/search/AdrSearchResultListItem.tsx index 3a11b5cf9a..118333a439 100644 --- a/plugins/adr/src/search/AdrSearchResultListItem.tsx +++ b/plugins/adr/src/search/AdrSearchResultListItem.tsx @@ -14,19 +14,19 @@ * limitations under the License. */ -import React from 'react'; +import React, { ReactNode } from 'react'; import { Box, Chip, Divider, ListItem, + ListItemIcon, ListItemText, makeStyles, } from '@material-ui/core'; import Typography from '@material-ui/core/Typography'; import { parseEntityRef } from '@backstage/catalog-model'; import { Link } from '@backstage/core-components'; -import { useAnalytics } from '@backstage/core-plugin-api'; import { AdrDocument } from '@backstage/plugin-adr-common'; import { humanizeEntityRef } from '@backstage/plugin-catalog-react'; import { ResultHighlight } from '@backstage/plugin-search-common'; @@ -43,35 +43,36 @@ const useStyles = makeStyles({ }, }); +/** + * @public + */ +export type AdrSearchResultListItemProps = { + lineClamp?: number; + highlight?: ResultHighlight; + icon?: ReactNode; + rank?: number; + result?: AdrDocument; +}; + /** * A component to display an ADR search result. * @public */ -export function AdrSearchResultListItem(props: { - lineClamp?: number; - highlight?: ResultHighlight; - rank?: number; - result: AdrDocument; -}) { - const { lineClamp = 5, highlight, rank, result } = props; +export function AdrSearchResultListItem(props: AdrSearchResultListItemProps) { + const { lineClamp = 5, highlight, icon, result } = props; const classes = useStyles(); - const analytics = useAnalytics(); - const handleClick = () => { - analytics.captureEvent('discover', result.title, { - attributes: { to: result.location }, - value: rank, - }); - }; + if (!result) return null; return ( <> + {icon && {icon}} + {highlight?.fields.title ? ( JSX.Element; + +// @public (undocumented) +export const TrpcApiDefinitionWidget: ( + props: TrpcApiDefinitionWidgetProps, +) => JSX.Element; + +// @public (undocumented) +export type TrpcApiDefinitionWidgetProps = { + definition: string; +}; ``` diff --git a/plugins/api-docs/dev/index.tsx b/plugins/api-docs/dev/index.tsx index 9975e91e98..995aa9c2fb 100644 --- a/plugins/api-docs/dev/index.tsx +++ b/plugins/api-docs/dev/index.tsx @@ -32,6 +32,7 @@ import graphqlApiEntity from './graphql-example-api.yaml'; import invalidLanguageApiEntity from './invalid-language-example-api.yaml'; import openapiApiEntity from './openapi-example-api.yaml'; import otherApiEntity from './other-example-api.yaml'; +import trpcApiEntity from './trpc-example-api.yaml'; const mockEntities = [ openapiApiEntity, @@ -39,6 +40,7 @@ const mockEntities = [ graphqlApiEntity, invalidLanguageApiEntity, otherApiEntity, + trpcApiEntity, ] as unknown as Entity[]; createDevApp() @@ -137,4 +139,17 @@ createDevApp() ), }) + .addPage({ + title: 'tRPC', + element: ( + +
+ + + + + + + ), + }) .render(); diff --git a/plugins/api-docs/dev/trpc-example-api.yaml b/plugins/api-docs/dev/trpc-example-api.yaml new file mode 100644 index 0000000000..f5807537b3 --- /dev/null +++ b/plugins/api-docs/dev/trpc-example-api.yaml @@ -0,0 +1,25 @@ +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: hello-world-trpc + description: Hello World example for tRPC +spec: + type: trpc + lifecycle: experimental + owner: team-c + definition: | + import { z } from 'zod'; + import { publicProcedure, router } from '../trpc'; + + export const apiRouter = router({ + version: publicProcedure.query(() => { + return { version: '0.42.0' }; + }), + hello: publicProcedure + .input(z.object({ username: z.string().nullish() }).nullish()) + .query(({ input, ctx }) => { + return { + text: `hello ${input?.username ?? ctx.user?.name ?? 'world'}`, + }; + }), + }); diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index e6dd3d6e1f..a23a123dc3 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -32,7 +32,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@asyncapi/react-component": "1.0.0-next.44", + "@asyncapi/react-component": "1.0.0-next.47", "@backstage/catalog-model": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionWidget.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionWidget.tsx index 03a56eaa78..8d7d7a7729 100644 --- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionWidget.tsx @@ -18,6 +18,7 @@ import { AsyncApiDefinitionWidget } from '../AsyncApiDefinitionWidget'; import { GraphQlDefinitionWidget } from '../GraphQlDefinitionWidget'; import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget'; import { GrpcApiDefinitionWidget } from '../GrpcApiDefinitionWidget'; +import { TrpcApiDefinitionWidget } from '../TrpcDefinitionWidget'; /** @public */ export type ApiDefinitionWidget = { @@ -61,5 +62,12 @@ export function defaultDefinitionWidgets(): ApiDefinitionWidget[] { ), }, + { + type: 'trpc', + title: 'tRPC', + component: definition => ( + + ), + }, ]; } diff --git a/plugins/api-docs/src/components/TrpcDefinitionWidget/TrpcApiDefinitionWidget.test.tsx b/plugins/api-docs/src/components/TrpcDefinitionWidget/TrpcApiDefinitionWidget.test.tsx new file mode 100644 index 0000000000..d7e6951e2e --- /dev/null +++ b/plugins/api-docs/src/components/TrpcDefinitionWidget/TrpcApiDefinitionWidget.test.tsx @@ -0,0 +1,32 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; +import React from 'react'; +import { TrpcApiDefinitionWidget } from './TrpcApiDefinitionWidget'; + +describe('', () => { + it('renders plain text', async () => { + const { getAllByText } = await renderInTestApp( + , + ); + + expect( + getAllByText((_text, element) => element?.textContent === 'Hello World') + .length, + ).toBeGreaterThan(0); + }); +}); diff --git a/plugins/api-docs/src/components/TrpcDefinitionWidget/TrpcApiDefinitionWidget.tsx b/plugins/api-docs/src/components/TrpcDefinitionWidget/TrpcApiDefinitionWidget.tsx new file mode 100644 index 0000000000..af73335dbd --- /dev/null +++ b/plugins/api-docs/src/components/TrpcDefinitionWidget/TrpcApiDefinitionWidget.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { CodeSnippet } from '@backstage/core-components'; +import { useTheme } from '@material-ui/core/styles'; +import { BackstageTheme } from '@backstage/theme'; + +/** @public */ +export type TrpcApiDefinitionWidgetProps = { + definition: string; +}; + +/** @public */ +export const TrpcApiDefinitionWidget = ( + props: TrpcApiDefinitionWidgetProps, +) => { + const { definition } = props; + const theme = useTheme(); + return ( + + ); +}; diff --git a/plugins/api-docs/src/components/TrpcDefinitionWidget/index.ts b/plugins/api-docs/src/components/TrpcDefinitionWidget/index.ts new file mode 100644 index 0000000000..604adb06f3 --- /dev/null +++ b/plugins/api-docs/src/components/TrpcDefinitionWidget/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2023 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 { + TrpcApiDefinitionWidget, + type TrpcApiDefinitionWidgetProps, +} from './TrpcApiDefinitionWidget'; diff --git a/plugins/api-docs/src/components/index.ts b/plugins/api-docs/src/components/index.ts index ea40076af4..7e7b206c54 100644 --- a/plugins/api-docs/src/components/index.ts +++ b/plugins/api-docs/src/components/index.ts @@ -22,3 +22,4 @@ export * from './ComponentsCards'; export * from './GraphQlDefinitionWidget'; export * from './OpenApiDefinitionWidget'; export * from './PlainApiDefinitionWidget'; +export * from './TrpcDefinitionWidget'; diff --git a/plugins/app-backend/src/service/appPlugin.ts b/plugins/app-backend/src/service/appPlugin.ts index b12a8d6877..a29f8fd5a8 100644 --- a/plugins/app-backend/src/service/appPlugin.ts +++ b/plugins/app-backend/src/service/appPlugin.ts @@ -71,7 +71,7 @@ export type AppPluginOptions = { * @alpha */ export const appPlugin = createBackendPlugin((options: AppPluginOptions) => ({ - id: 'app', + pluginId: 'app', register(env) { env.registerInit({ deps: { diff --git a/plugins/badges-backend/api-report.md b/plugins/badges-backend/api-report.md index 0ecf41ee85..8ca4b561a2 100644 --- a/plugins/badges-backend/api-report.md +++ b/plugins/badges-backend/api-report.md @@ -79,7 +79,7 @@ export type BadgeSpec = { }; // @public (undocumented) -export type BadgeStyle = typeof BADGE_STYLES[number]; +export type BadgeStyle = (typeof BADGE_STYLES)[number]; // @public (undocumented) export const createDefaultBadgeFactories: () => BadgeFactories; diff --git a/plugins/badges-backend/src/types.ts b/plugins/badges-backend/src/types.ts index eec7063f88..0d778289f9 100644 --- a/plugins/badges-backend/src/types.ts +++ b/plugins/badges-backend/src/types.ts @@ -27,7 +27,7 @@ export const BADGE_STYLES = [ ] as const; /** @public */ -export type BadgeStyle = typeof BADGE_STYLES[number]; +export type BadgeStyle = (typeof BADGE_STYLES)[number]; /** @public */ export interface Badge { diff --git a/plugins/bazaar-backend/src/plugin.ts b/plugins/bazaar-backend/src/plugin.ts index 03491950e8..62622717c8 100644 --- a/plugins/bazaar-backend/src/plugin.ts +++ b/plugins/bazaar-backend/src/plugin.ts @@ -27,7 +27,7 @@ import { createRouter } from './service/router'; * @alpha */ export const bazaarPlugin = createBackendPlugin({ - id: 'bazaar', + pluginId: 'bazaar', register(env) { env.registerInit({ deps: { diff --git a/plugins/bitbucket-cloud-common/api-report.md b/plugins/bitbucket-cloud-common/api-report.md index 5182579a4b..95e6f36836 100644 --- a/plugins/bitbucket-cloud-common/api-report.md +++ b/plugins/bitbucket-cloud-common/api-report.md @@ -132,7 +132,7 @@ export namespace Models { readonly Plaintext: 'plaintext'; }; export type BaseCommitSummaryMarkupEnum = - typeof BaseCommitSummaryMarkupEnum[keyof typeof BaseCommitSummaryMarkupEnum]; + (typeof BaseCommitSummaryMarkupEnum)[keyof typeof BaseCommitSummaryMarkupEnum]; export interface Branch { default_merge_strategy?: string; // (undocumented) @@ -150,7 +150,7 @@ export namespace Models { readonly FastForward: 'fast_forward'; }; export type BranchMergeStrategiesEnum = - typeof BranchMergeStrategiesEnum[keyof typeof BranchMergeStrategiesEnum]; + (typeof BranchMergeStrategiesEnum)[keyof typeof BranchMergeStrategiesEnum]; export interface Commit extends BaseCommit { // (undocumented) participants?: Array; @@ -179,7 +179,7 @@ export namespace Models { }; // (undocumented) export type CommitFileAttributesEnum = - typeof CommitFileAttributesEnum[keyof typeof CommitFileAttributesEnum]; + (typeof CommitFileAttributesEnum)[keyof typeof CommitFileAttributesEnum]; export interface Link { // (undocumented) href?: string; @@ -221,7 +221,7 @@ export namespace Models { }; // (undocumented) export type ParticipantRoleEnum = - typeof ParticipantRoleEnum[keyof typeof ParticipantRoleEnum]; + (typeof ParticipantRoleEnum)[keyof typeof ParticipantRoleEnum]; const // (undocumented) ParticipantStateEnum: { readonly Approved: 'approved'; @@ -230,7 +230,7 @@ export namespace Models { }; // (undocumented) export type ParticipantStateEnum = - typeof ParticipantStateEnum[keyof typeof ParticipantStateEnum]; + (typeof ParticipantStateEnum)[keyof typeof ParticipantStateEnum]; export interface Project extends ModelObject { // (undocumented) created_on?: string; @@ -306,7 +306,7 @@ export namespace Models { readonly NoForks: 'no_forks'; }; export type RepositoryForkPolicyEnum = - typeof RepositoryForkPolicyEnum[keyof typeof RepositoryForkPolicyEnum]; + (typeof RepositoryForkPolicyEnum)[keyof typeof RepositoryForkPolicyEnum]; const // (undocumented) RepositoryScmEnum: { readonly Git: 'git'; @@ -336,7 +336,7 @@ export namespace Models { } // (undocumented) export type RepositoryScmEnum = - typeof RepositoryScmEnum[keyof typeof RepositoryScmEnum]; + (typeof RepositoryScmEnum)[keyof typeof RepositoryScmEnum]; // (undocumented) export interface SearchCodeSearchResult { // (undocumented) diff --git a/plugins/bitbucket-cloud-common/src/models/index.ts b/plugins/bitbucket-cloud-common/src/models/index.ts index 9d25bbb873..d868e2926c 100644 --- a/plugins/bitbucket-cloud-common/src/models/index.ts +++ b/plugins/bitbucket-cloud-common/src/models/index.ts @@ -107,7 +107,7 @@ export namespace Models { * @public */ export type BaseCommitSummaryMarkupEnum = - typeof BaseCommitSummaryMarkupEnum[keyof typeof BaseCommitSummaryMarkupEnum]; + (typeof BaseCommitSummaryMarkupEnum)[keyof typeof BaseCommitSummaryMarkupEnum]; /** * A branch object, representing a branch in a repository. @@ -146,7 +146,7 @@ export namespace Models { * @public */ export type BranchMergeStrategiesEnum = - typeof BranchMergeStrategiesEnum[keyof typeof BranchMergeStrategiesEnum]; + (typeof BranchMergeStrategiesEnum)[keyof typeof BranchMergeStrategiesEnum]; /** * A repository commit object. @@ -191,7 +191,7 @@ export namespace Models { * @public */ export type CommitFileAttributesEnum = - typeof CommitFileAttributesEnum[keyof typeof CommitFileAttributesEnum]; + (typeof CommitFileAttributesEnum)[keyof typeof CommitFileAttributesEnum]; /** * A link to a resource related to this object. @@ -280,7 +280,7 @@ export namespace Models { * @public */ export type ParticipantRoleEnum = - typeof ParticipantRoleEnum[keyof typeof ParticipantRoleEnum]; + (typeof ParticipantRoleEnum)[keyof typeof ParticipantRoleEnum]; /** * @public @@ -295,7 +295,7 @@ export namespace Models { * @public */ export type ParticipantStateEnum = - typeof ParticipantStateEnum[keyof typeof ParticipantStateEnum]; + (typeof ParticipantStateEnum)[keyof typeof ParticipantStateEnum]; /** * A Bitbucket project. @@ -423,7 +423,7 @@ export namespace Models { * @public */ export type RepositoryForkPolicyEnum = - typeof RepositoryForkPolicyEnum[keyof typeof RepositoryForkPolicyEnum]; + (typeof RepositoryForkPolicyEnum)[keyof typeof RepositoryForkPolicyEnum]; /** * @public @@ -436,7 +436,7 @@ export namespace Models { * @public */ export type RepositoryScmEnum = - typeof RepositoryScmEnum[keyof typeof RepositoryScmEnum]; + (typeof RepositoryScmEnum)[keyof typeof RepositoryScmEnum]; /** * @public diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index 4f51867efc..00ad0ef032 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -31,7 +31,7 @@ export interface Config { host: string; /** * (Optional) Gitlab's group[/subgroup] where the discovery is done. - * If not defined the whole project will be scanned. + * If not defined the whole instance will be scanned. */ group?: string; /** diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/run.ts b/plugins/catalog-backend-module-incremental-ingestion/src/run.ts index 08d95f4417..2a58227862 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/run.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/run.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -// eslint-disable-next-line import/no-extraneous-dependencies +// eslint-disable-next-line @backstage/no-undeclared-imports import { databaseFactory, discoveryFactory, diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index a5abdc17c6..09770614b4 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -70,7 +70,11 @@ export const MICROSOFT_GRAPH_USER_ID_ANNOTATION = 'graph.microsoft.com/user-id'; export class MicrosoftGraphClient { constructor(baseUrl: string, tokenCredential: TokenCredential); static create(config: MicrosoftGraphProviderConfig): MicrosoftGraphClient; - getGroupMembers(groupId: string): AsyncIterable; + getGroupMembers( + groupId: string, + query?: ODataQuery, + queryMode?: 'basic' | 'advanced', + ): AsyncIterable; // (undocumented) getGroupPhoto(groupId: string, sizeId?: string): Promise; getGroupPhotoWithSizeLimit( @@ -81,6 +85,11 @@ export class MicrosoftGraphClient { query?: ODataQuery, queryMode?: 'basic' | 'advanced', ): AsyncIterable; + getGroupUserMembers( + groupId: string, + query?: ODataQuery, + queryMode?: 'basic' | 'advanced', + ): AsyncIterable; getOrganization(tenantId: string): Promise; // (undocumented) getUserPhoto(userId: string, sizeId?: string): Promise; @@ -88,10 +97,6 @@ export class MicrosoftGraphClient { userId: string, maxSize: number, ): Promise; - getUserProfile( - userId: string, - query?: ODataQuery, - ): Promise; getUsers( query?: ODataQuery, queryMode?: 'basic' | 'advanced', @@ -231,6 +236,7 @@ export type ODataQuery = { expand?: string; select?: string[]; count?: boolean; + top?: number; }; // @public diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts index fdaa2a6a43..f7d98f12c6 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts @@ -70,7 +70,7 @@ describe('MicrosoftGraphClient', () => { expect(await response.json()).toEqual({ value: 'example' }); }); - it('should perform api request with filter, select and expand', async () => { + it('should perform api request with filter, select, expand and top', async () => { worker.use( rest.get('https://example.com/users', (req, res, ctx) => res(ctx.status(200), ctx.json({ queryString: req.url.search })), @@ -81,12 +81,13 @@ describe('MicrosoftGraphClient', () => { filter: 'test eq true', expand: 'children', select: ['id', 'children'], + top: 471, }); expect(response.status).toBe(200); expect(await response.json()).toEqual({ queryString: - '?$filter=test%20eq%20true&$select=id,children&$expand=children', + '?$filter=test%20eq%20true&$select=id,children&$expand=children&$top=471', }); }); @@ -134,33 +135,6 @@ describe('MicrosoftGraphClient', () => { expect(values).toEqual(['first', 'second']); }); - it('should load user profile', async () => { - worker.use( - rest.get('https://example.com/users/user-id', (_, res, ctx) => - res( - ctx.status(200), - ctx.json({ - surname: 'Example', - }), - ), - ), - ); - - const userProfile = await client.getUserProfile('user-id'); - - expect(userProfile).toEqual({ surname: 'Example' }); - }); - - it('should throw exception if load user profile fails', async () => { - worker.use( - rest.get('https://example.com/users/user-id', (_, res, ctx) => - res(ctx.status(404)), - ), - ); - - await expect(() => client.getUserProfile('user-id')).rejects.toThrow(); - }); - it('should load user profile photo with max size of 120', async () => { worker.use( rest.get('https://example.com/users/user-id/photos', (_, res, ctx) => @@ -329,6 +303,27 @@ describe('MicrosoftGraphClient', () => { ]); }); + it('should load user group members', async () => { + worker.use( + rest.get( + 'https://example.com/groups/group-id/members/microsoft.graph.user', + (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + value: [{ id: '12345' }, { id: '67890' }], + }), + ), + ), + ); + + const values = await collectAsyncIterable( + client.getGroupUserMembers('group-id'), + ); + + expect(values).toEqual([{ id: '12345' }, { id: '67890' }]); + }); + it('should load organization', async () => { worker.use( rest.get('https://example.com/organization/tentant-id', (_, res, ctx) => diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts index 321d784543..7a685b258d 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts @@ -52,6 +52,10 @@ export type ODataQuery = { * Retrieves the total count of matching resources. */ count?: boolean; + /** + * Maximum number of records to receive in one batch. + */ + top?: number; }; /** @@ -188,6 +192,7 @@ export class MicrosoftGraphClient { $select: query?.select?.join(','), $expand: query?.expand, $count: query?.count, + $top: query?.top, }, { addQueryPrefix: true, @@ -229,28 +234,6 @@ export class MicrosoftGraphClient { }); } - /** - * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/user | User} - * from Graph API - * - * @public - * @param userId - The unique identifier for the `User` resource - * @param query - OData Query {@link ODataQuery} - * - */ - async getUserProfile( - userId: string, - query?: ODataQuery, - ): Promise { - const response = await this.requestApi(`users/${userId}`, query); - - if (response.status !== 200) { - await this.handleError('user profile', response); - } - - return await response.json(); - } - /** * Get {@link https://docs.microsoft.com/en-us/graph/api/resources/profilephoto | profilePhoto} * of `User` from Graph API with size limit @@ -343,8 +326,37 @@ export class MicrosoftGraphClient { * @param groupId - The unique identifier for the `Group` resource * */ - async *getGroupMembers(groupId: string): AsyncIterable { - yield* this.requestCollection(`groups/${groupId}/members`); + async *getGroupMembers( + groupId: string, + query?: ODataQuery, + queryMode?: 'basic' | 'advanced', + ): AsyncIterable { + yield* this.requestCollection( + `groups/${groupId}/members`, + query, + queryMode, + ); + } + + /** + * Get a collection of + * {@link https://docs.microsoft.com/en-us/graph/api/resources/user | User} + * belonging to a `Group` from Graph API and return as `AsyncIterable` + * @public + * @param groupId - The unique identifier for the `Group` resource + * @param query - OData Query {@link ODataQuery} + * @param queryMode - Mode to use while querying. Some features are only available at "advanced". + */ + async *getGroupUserMembers( + groupId: string, + query?: ODataQuery, + queryMode?: 'basic' | 'advanced', + ): AsyncIterable { + yield* this.requestCollection( + `groups/${groupId}/members/microsoft.graph.user/`, + query, + queryMode, + ); } /** diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts index 68c174665d..b02894dfd9 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts @@ -52,6 +52,7 @@ describe('readMicrosoftGraphConfig', () => { authority: 'https://login.example.com/', userExpand: 'manager', userFilter: 'accountEnabled eq true', + userSelect: ['id', 'displayName', 'department'], groupExpand: 'member', groupSelect: ['id', 'displayName', 'description'], groupFilter: 'securityEnabled eq false', @@ -69,6 +70,7 @@ describe('readMicrosoftGraphConfig', () => { authority: 'https://login.example.com/', userExpand: 'manager', userFilter: 'accountEnabled eq true', + userSelect: ['id', 'displayName', 'department'], groupExpand: 'member', groupSelect: ['id', 'displayName', 'description'], groupFilter: 'securityEnabled eq false', @@ -167,6 +169,7 @@ describe('readProviderConfigs', () => { user: { expand: 'manager', filter: 'accountEnabled eq true', + select: ['id', 'displayName', 'department'], }, group: { expand: 'member', @@ -196,6 +199,7 @@ describe('readProviderConfigs', () => { queryMode: 'advanced', userExpand: 'manager', userFilter: 'accountEnabled eq true', + userSelect: ['id', 'displayName', 'department'], groupExpand: 'member', groupSelect: ['id', 'displayName', 'description'], groupFilter: 'securityEnabled eq false', diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index 506951ff10..cf4ec646a2 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -264,6 +264,7 @@ export function readProviderConfig( const userExpand = config.getOptionalString('user.expand'); const userFilter = config.getOptionalString('user.filter'); + const userSelect = config.getOptionalStringArray('user.select'); const groupExpand = config.getOptionalString('group.expand'); const groupFilter = config.getOptionalString('group.filter'); @@ -318,6 +319,7 @@ export function readProviderConfig( tenantId, userExpand, userFilter, + userSelect, groupExpand, groupFilter, groupSearch, 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 bafb2ecaad..b1dfc5a148 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -61,9 +61,9 @@ function group(data: Partial): GroupEntity { describe('read microsoft graph', () => { const client: jest.Mocked = { getUsers: jest.fn(), - getUserProfile: jest.fn(), getGroups: jest.fn(), getGroupMembers: jest.fn(), + getGroupUserMembers: jest.fn(), getUserPhotoWithSizeLimit: jest.fn(), getGroupPhotoWithSizeLimit: jest.fn(), getOrganization: jest.fn(), @@ -78,13 +78,6 @@ describe('read microsoft graph', () => { mail: 'user.name@example.com', }; } - async function getExampleUserProfile() { - return { - id: 'userid', - displayName: 'User Name', - mail: 'user.name@example.com', - }; - } async function* getExampleGroups() { yield { id: 'groupid', @@ -140,6 +133,7 @@ describe('read microsoft graph', () => { expect(client.getUsers).toHaveBeenCalledWith( { filter: 'accountEnabled eq true', + top: 999, }, undefined, ); @@ -186,6 +180,7 @@ describe('read microsoft graph', () => { expect(client.getUsers).toHaveBeenCalledWith( { filter: 'accountEnabled eq true', + top: 999, }, 'advanced', ); @@ -228,6 +223,7 @@ describe('read microsoft graph', () => { { expand: 'manager', filter: 'accountEnabled eq true', + top: 999, }, undefined, ); @@ -242,9 +238,8 @@ describe('read microsoft graph', () => { describe('readMicrosoftGraphUsersInGroups', () => { it('should read users from Groups', async () => { client.getGroups.mockImplementation(getExampleGroups); - client.getGroupMembers.mockImplementation(getExampleGroupMembers); + client.getGroupUserMembers.mockImplementation(getExampleUsers); - client.getUserProfile.mockResolvedValue(getExampleUserProfile()); client.getUserPhotoWithSizeLimit.mockResolvedValue( 'data:image/jpeg;base64,...', ); @@ -278,16 +273,19 @@ describe('read microsoft graph', () => { expect(client.getGroups).toHaveBeenCalledWith( { filter: 'securityEnabled eq true', + select: ['id', 'displayName'], + top: 999, }, undefined, ); - expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid'); - expect(client.getUserProfile).toHaveBeenCalledTimes(1); - expect(client.getUserProfile).toHaveBeenCalledWith('userid', { - expand: undefined, - }); + expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1); + expect(client.getGroupUserMembers).toHaveBeenCalledWith( + 'groupid', + { top: 999 }, + undefined, + ); + expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( 'userid', @@ -297,13 +295,8 @@ describe('read microsoft graph', () => { it('should read users from Groups with advanced query mode', async () => { client.getGroups.mockImplementation(getExampleGroups); - client.getGroupMembers.mockImplementation(getExampleGroupMembers); + client.getGroupUserMembers.mockImplementation(getExampleUsers); - client.getUserProfile.mockResolvedValue({ - id: 'userid', - displayName: 'User Name', - mail: 'user.name@example.com', - }); client.getUserPhotoWithSizeLimit.mockResolvedValue( 'data:image/jpeg;base64,...', ); @@ -338,16 +331,19 @@ describe('read microsoft graph', () => { expect(client.getGroups).toHaveBeenCalledWith( { filter: 'securityEnabled eq true', + select: ['id', 'displayName'], + top: 999, }, 'advanced', ); - expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid'); - expect(client.getUserProfile).toHaveBeenCalledTimes(1); - expect(client.getUserProfile).toHaveBeenCalledWith('userid', { - expand: undefined, - }); + expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1); + expect(client.getGroupUserMembers).toHaveBeenCalledWith( + 'groupid', + { top: 999 }, + 'advanced', + ); + expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( 'userid', @@ -357,8 +353,8 @@ describe('read microsoft graph', () => { it('should read users with userExpand, groupExpand and custom transformer', async () => { client.getGroups.mockImplementation(getExampleGroups); - client.getGroupMembers.mockImplementation(getExampleGroupMembers); - client.getUserProfile.mockResolvedValue(getExampleUserProfile()); + client.getGroupUserMembers.mockImplementation(getExampleUsers); + client.getUserPhotoWithSizeLimit.mockResolvedValue( 'data:image/jpeg;base64,...', ); @@ -390,16 +386,22 @@ describe('read microsoft graph', () => { { expand: 'member', filter: 'securityEnabled eq true', + select: ['id', 'displayName'], + top: 999, + }, + undefined, + ); + + expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1); + expect(client.getGroupUserMembers).toHaveBeenCalledWith( + 'groupid', + { + expand: 'manager', + top: 999, }, undefined, ); - expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid'); - expect(client.getUserProfile).toHaveBeenCalledTimes(1); - expect(client.getUserProfile).toHaveBeenCalledWith('userid', { - expand: 'manager', - }); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( 'userid', @@ -527,11 +529,14 @@ describe('read microsoft graph', () => { expect(client.getGroups).toHaveBeenCalledWith( { filter: 'securityEnabled eq false', + top: 999, }, undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid'); + expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { + top: 999, + }); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -603,11 +608,14 @@ describe('read microsoft graph', () => { expect(client.getGroups).toHaveBeenCalledWith( { filter: 'securityEnabled eq false', + top: 999, }, 'advanced', ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid'); + expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { + top: 999, + }); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -680,11 +688,14 @@ describe('read microsoft graph', () => { { expand: 'member', filter: 'securityEnabled eq false', + top: 999, }, undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid'); + expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { + top: 999, + }); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -750,11 +761,14 @@ describe('read microsoft graph', () => { expect(client.getGroups).toHaveBeenCalledWith( { filter: 'securityEnabled eq true', + top: 999, }, undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid'); + expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { + top: 999, + }); }); }); @@ -887,6 +901,7 @@ describe('read microsoft graph', () => { expect(client.getUsers).toHaveBeenCalledWith( { filter: undefined, + top: 999, }, undefined, ); @@ -894,6 +909,7 @@ describe('read microsoft graph', () => { expect(client.getGroups).toHaveBeenCalledWith( { filter: 'securityEnabled eq false', + top: 999, }, undefined, ); @@ -925,6 +941,7 @@ describe('read microsoft graph', () => { { expand: 'manager', filter: 'accountEnabled eq true', + top: 999, }, undefined, ); @@ -932,6 +949,7 @@ describe('read microsoft graph', () => { expect(client.getGroups).toHaveBeenCalledWith( { filter: 'securityEnabled eq false', + top: 999, }, undefined, ); @@ -960,6 +978,7 @@ describe('read microsoft graph', () => { expect(client.getUsers).toHaveBeenCalledWith( { select: ['mail'], + top: 999, }, undefined, ); @@ -972,13 +991,13 @@ describe('read microsoft graph', () => { }); client.getUsers.mockImplementation(getExampleUsers); - client.getUserProfile.mockImplementation(getExampleUserProfile); client.getUserPhotoWithSizeLimit.mockResolvedValue( 'data:image/jpeg;base64,...', ); client.getGroups.mockImplementation(getExampleGroups); client.getGroupMembers.mockImplementation(getExampleGroupMembers); + client.getGroupUserMembers.mockImplementation(getExampleUsers); client.getGroupPhotoWithSizeLimit.mockResolvedValue( 'data:image/jpeg;base64,...', ); @@ -994,16 +1013,18 @@ describe('read microsoft graph', () => { expect(client.getGroups).toHaveBeenCalledWith( { filter: 'name eq backstage-group', + select: ['id', 'displayName'], + top: 999, }, undefined, ); expect(client.getGroups).toHaveBeenCalledWith( { filter: 'securityEnabled eq false', + top: 999, }, undefined, ); - expect(client.getUserProfile).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 6699af7764..003448a3e8 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -38,6 +38,9 @@ import { defaultOrganizationTransformer, defaultUserTransformer, } from './defaultTransformers'; +import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; + +const PAGE_SIZE = 999; export async function readMicrosoftGraphUsers( client: MicrosoftGraphClient, @@ -52,50 +55,24 @@ export async function readMicrosoftGraphUsers( ): Promise<{ users: UserEntity[]; // With all relations empty }> { - const users: UserEntity[] = []; - const limiter = limiterFactory(10); - - const transformer = options.transformer ?? defaultUserTransformer; - const promises: Promise[] = []; - - for await (const user of client.getUsers( + const users = client.getUsers( { filter: options.userFilter, expand: options.userExpand, select: options.userSelect, + top: PAGE_SIZE, }, options.queryMode, - )) { - // Process all users in parallel, otherwise it can take quite some time - promises.push( - limiter(async () => { - 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}, ${e}`); - } + ); - const entity = await transformer(user, userPhoto); - - if (!entity) { - return; - } - - users.push(entity); - }), - ); - } - - // Wait for all users and photos to be downloaded - await Promise.all(promises); - - return { users }; + return { + users: await transformUsers( + client, + users, + options.logger, + options.transformer, + ), + }; } export async function readMicrosoftGraphUsersInGroups( @@ -113,36 +90,41 @@ export async function readMicrosoftGraphUsersInGroups( ): Promise<{ users: UserEntity[]; // With all relations empty }> { - const users: UserEntity[] = []; - const limiter = limiterFactory(10); - const transformer = options.transformer ?? defaultUserTransformer; const userGroupMemberPromises: Promise[] = []; - const userPromises: Promise[] = []; - - const groupMemberUsers: Set = new Set(); + const userGroupMembers = new Map(); for await (const group of client.getGroups( { expand: options.groupExpand, search: options.userGroupMemberSearch, filter: options.userGroupMemberFilter, + select: ['id', 'displayName'], + top: PAGE_SIZE, }, options.queryMode, )) { // Process all groups in parallel, otherwise it can take quite some time userGroupMemberPromises.push( limiter(async () => { - for await (const member of client.getGroupMembers(group.id!)) { - if (!member.id) { - continue; - } - - if (member['@odata.type'] === '#microsoft.graph.user') { - groupMemberUsers.add(member.id); - } + let groupMemberCount = 0; + for await (const user of client.getGroupUserMembers( + group.id!, + { + expand: options.userExpand, + top: PAGE_SIZE, + }, + options.queryMode, + )) { + userGroupMembers.set(user.id!, user); + groupMemberCount++; } + options.logger.debug('Read users from group', { + groupId: group.id, + groupName: group.displayName, + memberCount: groupMemberCount, + }); }), ); } @@ -150,47 +132,19 @@ export async function readMicrosoftGraphUsersInGroups( // Wait for all group members await Promise.all(userGroupMemberPromises); - options.logger.info(`groupMemberUsers ${groupMemberUsers.size}`); - for (const userId of groupMemberUsers) { - // Process all users in parallel, otherwise it can take quite some time - userPromises.push( - limiter(async () => { - let user; - let userPhoto; - try { - user = await client.getUserProfile(userId, { - expand: options.userExpand, - }); - } catch (e) { - options.logger.warn(`Unable to load user for ${userId}, ${e}`); - } - if (user) { - 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 userphoto for ${userId}, ${e}`); - } + options.logger.info('Read users from group membership', { + groupCount: userGroupMemberPromises.length, + userCount: userGroupMembers.size, + }); - const entity = await transformer(user, userPhoto); - - if (!entity) { - return; - } - users.push(entity); - } - }), - ); - } - - // Wait for all users and photos to be downloaded - await Promise.all(userPromises); - - return { users }; + return { + users: await transformUsers( + client, + userGroupMembers.values(), + options.logger, + options.transformer, + ), + }; } export async function readMicrosoftGraphOrganization( @@ -248,6 +202,7 @@ export async function readMicrosoftGraphGroups( search: options?.groupSearch, filter: options?.groupFilter, select: options?.groupSelect, + top: PAGE_SIZE, }, options?.queryMode, )) { @@ -269,7 +224,9 @@ export async function readMicrosoftGraphGroups( return; } - for await (const member of client.getGroupMembers(group.id!)) { + for await (const member of client.getGroupMembers(group.id!, { + top: PAGE_SIZE, + })) { if (!member.id) { continue; } @@ -461,6 +418,56 @@ export async function readMicrosoftGraphOrg( return { users, groups }; } +async function transformUsers( + client: MicrosoftGraphClient, + users: Iterable | AsyncIterable, + logger: Logger, + transformer?: UserTransformer, +) { + const limiter = limiterFactory(10); + + const resolvedTransformer = transformer ?? defaultUserTransformer; + const promises: Promise[] = []; + const entities: UserEntity[] = []; + + // Process all users in parallel, otherwise it can take quite some time + for await (const user of users) { + promises.push( + limiter(async () => { + 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) { + logger.warn(`Unable to load user photo for`, { + user: user.id, + error: e, + }); + } + + const entity = await resolvedTransformer(user, userPhoto); + + if (entity) { + entities.push(entity); + } + }), + ); + } + + // Wait for all users and photos to be downloaded + await Promise.all(promises); + + logger.debug('Finished transforming users', { + microsoftUserCount: promises.length, + backstageUserCount: entities.length, + }); + return entities; +} + function ensureItem( target: Map>, key: string, diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index effed7f8f1..5abb630668 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -56,7 +56,7 @@ class CatalogExtensionPointImpl implements CatalogProcessingExtensionPoint { * @alpha */ export const catalogPlugin = createBackendPlugin({ - id: 'catalog', + pluginId: 'catalog', register(env) { const processingExtensions = new CatalogExtensionPointImpl(); // plugins depending on this API will be initialized before this plugins init method is executed. @@ -97,11 +97,7 @@ export const catalogPlugin = createBackendPlugin({ const { processingEngine, router } = await builder.build(); await processingEngine.start(); - lifecycle.addShutdownHook({ - fn: async () => { - await processingEngine.stop(); - }, - }); + lifecycle.addShutdownHook(() => processingEngine.stop()); httpRouter.use(router); }, }); diff --git a/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx b/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx index adc8a569bb..22f69ac30f 100644 --- a/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx +++ b/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx @@ -212,6 +212,14 @@ const generatedColumns: TableColumn[] = [ { title: 'Workflow', field: 'workflow.name', + highlight: true, + render: (row: Partial) => ( + + {row?.workflow?.name} + + ), }, { title: 'Actions', diff --git a/plugins/code-climate/dev/index.tsx b/plugins/code-climate/dev/index.tsx index d5fc4c28e3..060679337e 100644 --- a/plugins/code-climate/dev/index.tsx +++ b/plugins/code-climate/dev/index.tsx @@ -15,7 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; -// eslint-disable-next-line import/no-extraneous-dependencies +// eslint-disable-next-line @backstage/no-undeclared-imports import { createDevApp, EntityGridItem } from '@backstage/dev-utils'; import { Grid } from '@material-ui/core'; import React from 'react'; diff --git a/plugins/code-climate/src/setupTests.ts b/plugins/code-climate/src/setupTests.ts index 43d9e09713..eb4654b869 100644 --- a/plugins/code-climate/src/setupTests.ts +++ b/plugins/code-climate/src/setupTests.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ import '@testing-library/jest-dom'; import 'cross-fetch/polyfill'; diff --git a/plugins/cost-insights/src/example/templates/CostInsightsClient.ts b/plugins/cost-insights/src/example/templates/CostInsightsClient.ts index 50c3bb1470..046196f26b 100644 --- a/plugins/cost-insights/src/example/templates/CostInsightsClient.ts +++ b/plugins/cost-insights/src/example/templates/CostInsightsClient.ts @@ -22,7 +22,7 @@ // IMPORTANT: Remove the lines below to enable type checking and linting // @ts-nocheck -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ import { CostInsightsApi, diff --git a/plugins/events-backend/src/service/EventsPlugin.ts b/plugins/events-backend/src/service/EventsPlugin.ts index 29456d71f7..402104b56c 100644 --- a/plugins/events-backend/src/service/EventsPlugin.ts +++ b/plugins/events-backend/src/service/EventsPlugin.ts @@ -80,7 +80,7 @@ class EventsExtensionPointImpl implements EventsExtensionPoint { * @alpha */ export const eventsPlugin = createBackendPlugin({ - id: 'events', + pluginId: 'events', register(env) { const extensionPoint = new EventsExtensionPointImpl(); env.registerExtensionPoint(eventsExtensionPoint, extensionPoint); diff --git a/plugins/example-todo-list-backend/src/plugin.ts b/plugins/example-todo-list-backend/src/plugin.ts index ba25ed7218..42985f6e88 100644 --- a/plugins/example-todo-list-backend/src/plugin.ts +++ b/plugins/example-todo-list-backend/src/plugin.ts @@ -27,7 +27,7 @@ import { createRouter } from './service/router'; * @alpha */ export const exampleTodoListPlugin = createBackendPlugin({ - id: 'exampleTodoList', + pluginId: 'exampleTodoList', register(env) { env.registerInit({ deps: { diff --git a/plugins/example-todo-list/.eslintrc.js b/plugins/example-todo-list/.eslintrc.js index 8c18e76229..e2a53a6ad2 100644 --- a/plugins/example-todo-list/.eslintrc.js +++ b/plugins/example-todo-list/.eslintrc.js @@ -1,14 +1 @@ -module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint')], - rules: { - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: true, - optionalDependencies: true, - peerDependencies: true, - bundledDependencies: true, - }, - ], - }, -}; +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/kafka-backend/src/plugin.ts b/plugins/kafka-backend/src/plugin.ts index f627e5a0bd..2442eddbaa 100644 --- a/plugins/kafka-backend/src/plugin.ts +++ b/plugins/kafka-backend/src/plugin.ts @@ -27,7 +27,7 @@ import { createRouter } from './service/router'; * @alpha */ export const kafkaPlugin = createBackendPlugin({ - id: 'kafka', + pluginId: 'kafka', register(env) { env.registerInit({ deps: { diff --git a/plugins/periskop-backend/src/plugin.ts b/plugins/periskop-backend/src/plugin.ts index b5dcfb1425..933c864d50 100644 --- a/plugins/periskop-backend/src/plugin.ts +++ b/plugins/periskop-backend/src/plugin.ts @@ -27,7 +27,7 @@ import { createRouter } from './service/router'; * @alpha */ export const periskopPlugin = createBackendPlugin({ - id: 'periskop', + pluginId: 'periskop', register(env) { env.registerInit({ deps: { diff --git a/plugins/proxy-backend/src/plugin.ts b/plugins/proxy-backend/src/plugin.ts index e9d6d515c1..d095bee527 100644 --- a/plugins/proxy-backend/src/plugin.ts +++ b/plugins/proxy-backend/src/plugin.ts @@ -28,7 +28,7 @@ import { createRouter } from './service/router'; */ export const proxyPlugin = createBackendPlugin( (options?: { skipInvalidProxies?: boolean }) => ({ - id: 'proxy', + pluginId: 'proxy', register(env) { env.registerInit({ deps: { diff --git a/plugins/scaffolder-backend/scripts/build-nunjucks.js b/plugins/scaffolder-backend/scripts/build-nunjucks.js index 8b98ac4305..8183de77ee 100755 --- a/plugins/scaffolder-backend/scripts/build-nunjucks.js +++ b/plugins/scaffolder-backend/scripts/build-nunjucks.js @@ -16,7 +16,7 @@ */ /* eslint-disable no-restricted-syntax */ -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ const path = require('path'); diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 43b8d30f99..3b0d26be21 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -64,7 +64,7 @@ class ScaffolderActionsExtensionPointImpl */ export const scaffolderPlugin = createBackendPlugin( (options: ScaffolderPluginOptions) => ({ - id: 'scaffolder', + pluginId: 'scaffolder', register(env) { const actionsExtensions = new ScaffolderActionsExtensionPointImpl(); diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index 1dbd145bce..b4772dd62d 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -74,6 +74,23 @@ describe('createRouter', () => { }); describe('GET /query', () => { + it('throws meaningful query errors', async () => { + const error = new Error('Query error message'); + mockSearchEngine.query.mockRejectedValueOnce(error); + + const response = await request(app).get('/query'); + + expect(response.status).toEqual(500); + expect(response.body).toMatchObject( + expect.objectContaining({ + error: { + name: 'Error', + message: `There was a problem performing the search query: ${error.message}`, + }, + }), + ); + }); + it('returns empty results array', async () => { const response = await request(app).get('/query'); diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index b51dcead14..10f47d180f 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -184,7 +184,7 @@ export async function createRouter( } throw new Error( - `There was a problem performing the search query. ${error}`, + `There was a problem performing the search query: ${error.message}`, ); } }, diff --git a/plugins/splunk-on-call/src/components/TriggerDialog/testUtils.ts b/plugins/splunk-on-call/src/components/TriggerDialog/testUtils.ts index 764ac94e2c..9beb4e150e 100644 --- a/plugins/splunk-on-call/src/components/TriggerDialog/testUtils.ts +++ b/plugins/splunk-on-call/src/components/TriggerDialog/testUtils.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// eslint-disable-next-line import/no-extraneous-dependencies +// eslint-disable-next-line @backstage/no-undeclared-imports import { act, fireEvent, diff --git a/plugins/tech-radar/api-report.md b/plugins/tech-radar/api-report.md index e2744872d8..3b84990d85 100644 --- a/plugins/tech-radar/api-report.md +++ b/plugins/tech-radar/api-report.md @@ -25,7 +25,8 @@ export interface RadarEntry { quadrant: string; timeline: Array; title: string; - url: string; + // @deprecated + url?: string; } // @public diff --git a/plugins/tech-radar/src/api.ts b/plugins/tech-radar/src/api.ts index 0fbc88255a..c0b8fb8852 100644 --- a/plugins/tech-radar/src/api.ts +++ b/plugins/tech-radar/src/api.ts @@ -133,8 +133,10 @@ export interface RadarEntry { * @remarks * * You can use `#` if you don't want to provide any other url + * + * @deprecated Use {@link RadarEntry.links} instead */ - url: string; + url?: string; /** * History of the Entry moving through {@link RadarRing} */ diff --git a/plugins/tech-radar/src/sample.ts b/plugins/tech-radar/src/sample.ts index 1c4f7a1a01..395b4f2ff5 100644 --- a/plugins/tech-radar/src/sample.ts +++ b/plugins/tech-radar/src/sample.ts @@ -45,12 +45,15 @@ entries.push({ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua', }, ], - url: 'https://www.javascript.com/', key: 'javascript', id: 'javascript', title: 'JavaScript', quadrant: 'languages', links: [ + { + url: 'https://www.javascript.com/', + title: 'Learn more', + }, { url: 'https://www.typescriptlang.org/', title: 'TypeScript', @@ -69,7 +72,6 @@ entries.push({ 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat', }, ], - url: '#', key: 'typescript', id: 'typescript', title: 'TypeScript', @@ -87,7 +89,12 @@ entries.push({ 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur', }, ], - url: 'https://webpack.js.org/', + links: [ + { + url: 'https://webpack.js.org/', + title: 'Learn more', + }, + ], key: 'webpack', id: 'webpack', title: 'Webpack', @@ -101,7 +108,12 @@ entries.push({ date: new Date('2020-08-06'), }, ], - url: 'https://reactjs.org/', + links: [ + { + url: 'https://reactjs.org/', + title: 'Learn more', + }, + ], key: 'react', id: 'react', title: 'React', @@ -115,7 +127,6 @@ entries.push({ date: new Date('2020-08-06'), }, ], - url: '#', key: 'code-reviews', id: 'code-reviews', title: 'Code Reviews', @@ -129,7 +140,6 @@ entries.push({ date: new Date('2020-08-06'), }, ], - url: '#', key: 'mob-programming', id: 'mob-programming', title: 'Mob Programming', @@ -143,7 +153,6 @@ entries.push({ date: new Date('2020-08-06'), }, ], - url: '#', key: 'docs-like-code', id: 'docs-like-code', title: 'Docs-like-code', @@ -156,7 +165,6 @@ entries.push({ date: new Date('2020-08-06'), }, ], - url: '#', key: 'force-push', id: 'force-push', title: 'Force push to master', @@ -170,7 +178,12 @@ entries.push({ description: 'long description', }, ], - url: 'https://github.com', + links: [ + { + url: 'https://github.com', + title: 'Learn more', + }, + ], key: 'github-actions', id: 'github-actions', title: 'GitHub Actions', diff --git a/plugins/user-settings-backend/src/plugin.ts b/plugins/user-settings-backend/src/plugin.ts index 83120cc180..e200d22ffe 100644 --- a/plugins/user-settings-backend/src/plugin.ts +++ b/plugins/user-settings-backend/src/plugin.ts @@ -26,7 +26,7 @@ import { createRouter } from './service/router'; * @alpha */ export const userSettingsPlugin = createBackendPlugin({ - id: 'userSettings', + pluginId: 'userSettings', register(env) { env.registerInit({ deps: { diff --git a/scripts/assemble-manifest.js b/scripts/assemble-manifest.js index f68a83447d..f01bc7fd9b 100755 --- a/scripts/assemble-manifest.js +++ b/scripts/assemble-manifest.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ /* * Copyright 2022 The Backstage Authors * diff --git a/scripts/check-docs-quality.js b/scripts/check-docs-quality.js index 10c4b99976..3dcbd6ef81 100755 --- a/scripts/check-docs-quality.js +++ b/scripts/check-docs-quality.js @@ -56,7 +56,7 @@ async function listFiles(dir = '') { // caused by the script. In CI, we want to ensure vale linter is run. async function exitIfMissingVale() { try { - // eslint-disable-next-line import/no-extraneous-dependencies + // eslint-disable-next-line @backstage/no-undeclared-imports await require('command-exists')('vale'); } catch (e) { if (process.env.CI) { diff --git a/scripts/check-if-release.js b/scripts/check-if-release.js index e7dabe14a4..c12b27b0ef 100755 --- a/scripts/check-if-release.js +++ b/scripts/check-if-release.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ /* * Copyright 2020 The Backstage Authors * diff --git a/scripts/create-github-release.js b/scripts/create-github-release.js index 4b3891c5ce..b64d7a9cb5 100755 --- a/scripts/create-github-release.js +++ b/scripts/create-github-release.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ /* * Copyright 2020 The Backstage Authors * diff --git a/scripts/create-release-tag.js b/scripts/create-release-tag.js index 28417024fd..311b46d4e5 100755 --- a/scripts/create-release-tag.js +++ b/scripts/create-release-tag.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ /* * Copyright 2020 The Backstage Authors * diff --git a/scripts/list-deprecations.js b/scripts/list-deprecations.js index 8047158138..22aff96d67 100755 --- a/scripts/list-deprecations.js +++ b/scripts/list-deprecations.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ const _ = require('lodash'); const fs = require('fs-extra'); diff --git a/scripts/prepare-release.js b/scripts/prepare-release.js index 973f4fad58..4019a71cf3 100755 --- a/scripts/prepare-release.js +++ b/scripts/prepare-release.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ /* * Copyright 2022 The Backstage Authors * diff --git a/scripts/snyk-github-issue-sync.ts b/scripts/snyk-github-issue-sync.ts index da64e2ea82..ea7d42a2a4 100755 --- a/scripts/snyk-github-issue-sync.ts +++ b/scripts/snyk-github-issue-sync.ts @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ import { Octokit } from '@octokit/rest'; import minimist from 'minimist'; // Generated by GitHub workflow .github/workflows/snyk-github-issue-creator diff --git a/scripts/verify-api-reference.js b/scripts/verify-api-reference.js index 84a526604d..091e803c33 100755 --- a/scripts/verify-api-reference.js +++ b/scripts/verify-api-reference.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ const { resolve: resolvePath } = require('path'); const { promises: fs } = require('fs'); diff --git a/scripts/verify-changesets.js b/scripts/verify-changesets.js index 9e3064eeac..ae3f07840a 100755 --- a/scripts/verify-changesets.js +++ b/scripts/verify-changesets.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ const { resolve: resolvePath } = require('path'); const fs = require('fs-extra'); diff --git a/scripts/verify-links.js b/scripts/verify-links.js index 05ad387c37..7664cadad5 100755 --- a/scripts/verify-links.js +++ b/scripts/verify-links.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ const { resolve: resolvePath, join: joinPath, dirname } = require('path'); const fs = require('fs').promises; diff --git a/scripts/verify-lockfile-duplicates.js b/scripts/verify-lockfile-duplicates.js index cc22664219..820fba2ea5 100644 --- a/scripts/verify-lockfile-duplicates.js +++ b/scripts/verify-lockfile-duplicates.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @backstage/no-undeclared-imports */ const { execFile: execFileCb } = require('child_process'); const { resolve: resolvePath, dirname: dirnamePath } = require('path'); diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 7c73f5d0cc..8e5b2b8c56 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -6783,9 +6783,9 @@ __metadata: linkType: hard "http-cache-semantics@npm:^4.1.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index cdf50e0c78..6e252cb1c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -217,14 +217,16 @@ __metadata: languageName: node linkType: hard -"@asyncapi/avro-schema-parser@npm:^0.3.0": - version: 0.3.0 - resolution: "@asyncapi/avro-schema-parser@npm:0.3.0" - checksum: 3086bc16a634600fe4d7c83f52f9558684c0d79193958fcb9275cacfb550700eaa718aa9ed1d3c89d1d77253f67e0f33ea91e137ef08aa722d67ded9373bf142 +"@asyncapi/avro-schema-parser@npm:^1.1.0": + version: 1.1.0 + resolution: "@asyncapi/avro-schema-parser@npm:1.1.0" + dependencies: + avsc: ^5.7.3 + checksum: 31bb6285a49f255e809088164ac4638103b2ec3e761c9c85be116af8abd9fe429626ba79ba6b3899d3823a77216741d9d793841e600145f2956328f3ce45a0e0 languageName: node linkType: hard -"@asyncapi/openapi-schema-parser@npm:^2.0.0": +"@asyncapi/openapi-schema-parser@npm:^2.0.1": version: 2.0.1 resolution: "@asyncapi/openapi-schema-parser@npm:2.0.1" dependencies: @@ -233,12 +235,12 @@ __metadata: languageName: node linkType: hard -"@asyncapi/parser@npm:^1.17.0": - version: 1.17.0 - resolution: "@asyncapi/parser@npm:1.17.0" +"@asyncapi/parser@npm:^1.18.0": + version: 1.18.0 + resolution: "@asyncapi/parser@npm:1.18.0" dependencies: "@apidevtools/json-schema-ref-parser": ^9.0.6 - "@asyncapi/specs": ^3.2.0 + "@asyncapi/specs": ^4.1.0 "@fmvilas/pseudo-yaml-ast": ^0.3.1 ajv: ^6.10.1 js-yaml: ^3.13.1 @@ -246,34 +248,35 @@ __metadata: lodash.clonedeep: ^4.5.0 node-fetch: ^2.6.0 tiny-merge-patch: ^0.1.2 - checksum: 6ac0bd3c9c90abf331cfdfb054dab047cf0324ee803633c2abc6dad96b12c64d033e4fb94907906adfb521ba16e9b23083b2a6acc7719ccd22154a7867046a95 + checksum: 7e08cae52990ce97299170faf99aaf27a8e2e02e6b03df28518d256f5687f394c26fd0a4aba235514ed4e3c2fd081a79321a489c20987ed22122afdb0b9ea0dd languageName: node linkType: hard -"@asyncapi/react-component@npm:1.0.0-next.44": - version: 1.0.0-next.44 - resolution: "@asyncapi/react-component@npm:1.0.0-next.44" +"@asyncapi/react-component@npm:1.0.0-next.47": + version: 1.0.0-next.47 + resolution: "@asyncapi/react-component@npm:1.0.0-next.47" dependencies: - "@asyncapi/avro-schema-parser": ^0.3.0 - "@asyncapi/openapi-schema-parser": ^2.0.0 - "@asyncapi/parser": ^1.17.0 + "@asyncapi/avro-schema-parser": ^1.1.0 + "@asyncapi/openapi-schema-parser": ^2.0.1 + "@asyncapi/parser": ^1.18.0 highlight.js: ^10.7.2 isomorphic-dompurify: ^0.13.0 marked: ^4.0.14 openapi-sampler: ^1.2.1 - react-icons: ^4.4.0 use-resize-observer: ^8.0.0 peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 1d96db3bc6a4655884969a7639467836376c028df1a00f8930adf4e1b6ecb43e6a2d61df5c68e63d6c105d319fecb3f2ac0e4146274a81a874fbde17f4dbac1c + checksum: 837b6378f1188945a0e92d93efe71691a8dad120fad5624b58b325ec86c51435b874464ec0c47308ffacda9ee4d77ad8217881584bd74081f6db2211f09cc60c languageName: node linkType: hard -"@asyncapi/specs@npm:^3.2.0": - version: 3.2.0 - resolution: "@asyncapi/specs@npm:3.2.0" - checksum: 09971262aefc8844ab3e7c0c3652711862ac562dd5d614f23b496185690430a81df8e50eddba657f4141e0fd9548ef622fe6c20f4e3dec8054be23f774798335 +"@asyncapi/specs@npm:^4.1.0": + version: 4.1.0 + resolution: "@asyncapi/specs@npm:4.1.0" + dependencies: + "@types/json-schema": ^7.0.11 + checksum: 6e95f3c1ef7267480cdfc69f5a015f63b9101874289e31843a629346a3ea07490e3043b296a089bf3458e877c664d83d4b4738dcb53d37d7e13b75c7bc08c879 languageName: node linkType: hard @@ -3295,7 +3298,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.20.13 resolution: "@babel/runtime@npm:7.20.13" dependencies: @@ -3420,6 +3423,7 @@ __metadata: resolution: "@backstage/backend-common@workspace:packages/backend-common" dependencies: "@backstage/backend-app-api": "workspace:^" + "@backstage/backend-dev-utils": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -3516,6 +3520,14 @@ __metadata: languageName: unknown linkType: soft +"@backstage/backend-dev-utils@workspace:^, @backstage/backend-dev-utils@workspace:packages/backend-dev-utils": + version: 0.0.0-use.local + resolution: "@backstage/backend-dev-utils@workspace:packages/backend-dev-utils" + dependencies: + "@backstage/cli": "workspace:^" + languageName: unknown + linkType: soft + "@backstage/backend-plugin-api@workspace:^, @backstage/backend-plugin-api@workspace:packages/backend-plugin-api": version: 0.0.0-use.local resolution: "@backstage/backend-plugin-api@workspace:packages/backend-plugin-api" @@ -3657,10 +3669,12 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/eslint-plugin": "workspace:^" "@backstage/release-manifests": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@backstage/types": "workspace:^" + "@esbuild-kit/cjs-loader": ^2.4.1 "@manypkg/get-packages": ^1.1.3 "@octokit/request": ^6.0.0 "@pmmmwh/react-refresh-webpack-plugin": ^0.5.7 @@ -3679,6 +3693,7 @@ __metadata: "@swc/core": ^1.3.9 "@swc/helpers": ^0.4.7 "@swc/jest": ^0.2.22 + "@types/cross-spawn": ^6.0.2 "@types/diff": ^5.0.0 "@types/express": ^4.17.6 "@types/fs-extra": ^9.0.1 @@ -3706,6 +3721,7 @@ __metadata: chalk: ^4.0.0 chokidar: ^3.3.1 commander: ^9.1.0 + cross-spawn: ^7.0.3 css-loader: ^6.5.1 del: ^6.0.0 diff: ^5.0.0 @@ -3718,7 +3734,6 @@ __metadata: eslint-plugin-import: ^2.25.4 eslint-plugin-jest: ^27.0.0 eslint-plugin-jsx-a11y: ^6.5.1 - eslint-plugin-monorepo: ^0.3.2 eslint-plugin-react: ^7.28.0 eslint-plugin-react-hooks: ^4.3.0 eslint-webpack-plugin: ^3.1.1 @@ -4092,6 +4107,17 @@ __metadata: languageName: unknown linkType: soft +"@backstage/eslint-plugin@workspace:^, @backstage/eslint-plugin@workspace:packages/eslint-plugin": + version: 0.0.0-use.local + resolution: "@backstage/eslint-plugin@workspace:packages/eslint-plugin" + dependencies: + "@backstage/cli": "workspace:^" + "@manypkg/get-packages": ^1.1.3 + eslint: ^8.33.0 + minimatch: ^5.1.2 + languageName: unknown + linkType: soft + "@backstage/integration-aws-node@workspace:^, @backstage/integration-aws-node@workspace:packages/integration-aws-node": version: 0.0.0-use.local resolution: "@backstage/integration-aws-node@workspace:packages/integration-aws-node" @@ -4247,6 +4273,7 @@ __metadata: react-use: ^17.2.4 remark-gfm: ^3.0.1 peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 languageName: unknown @@ -4401,7 +4428,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-api-docs@workspace:plugins/api-docs" dependencies: - "@asyncapi/react-component": 1.0.0-next.44 + "@asyncapi/react-component": 1.0.0-next.47 "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" @@ -9184,9 +9211,29 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/android-arm64@npm:0.17.5" +"@esbuild-kit/cjs-loader@npm:^2.4.1": + version: 2.4.2 + resolution: "@esbuild-kit/cjs-loader@npm:2.4.2" + dependencies: + "@esbuild-kit/core-utils": ^3.0.0 + get-tsconfig: ^4.4.0 + checksum: e346e339bfc7eff5c52c270fd0ec06a7f2341b624adfb69f84b7d83f119c35070420906f2761a0b4604e0a0ec90e35eaf12544585476c428ed6d6ee3b250c0fe + languageName: node + linkType: hard + +"@esbuild-kit/core-utils@npm:^3.0.0": + version: 3.0.0 + resolution: "@esbuild-kit/core-utils@npm:3.0.0" + dependencies: + esbuild: ~0.15.10 + source-map-support: ^0.5.21 + checksum: 0e89ec718e2211bf95c48a8085aaef88e8e416f42abd1c62d488d5458eecd3fbc144179a0c5570ad36fa7e2d3bbc411f8d3fb28802c37ced2154dc2c6ded9dfe + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/android-arm64@npm:0.17.6" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -9198,65 +9245,65 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/android-arm@npm:0.17.5" +"@esbuild/android-arm@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/android-arm@npm:0.17.6" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/android-x64@npm:0.17.5" +"@esbuild/android-x64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/android-x64@npm:0.17.6" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/darwin-arm64@npm:0.17.5" +"@esbuild/darwin-arm64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/darwin-arm64@npm:0.17.6" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/darwin-x64@npm:0.17.5" +"@esbuild/darwin-x64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/darwin-x64@npm:0.17.6" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/freebsd-arm64@npm:0.17.5" +"@esbuild/freebsd-arm64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/freebsd-arm64@npm:0.17.6" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/freebsd-x64@npm:0.17.5" +"@esbuild/freebsd-x64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/freebsd-x64@npm:0.17.6" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/linux-arm64@npm:0.17.5" +"@esbuild/linux-arm64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/linux-arm64@npm:0.17.6" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/linux-arm@npm:0.17.5" +"@esbuild/linux-arm@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/linux-arm@npm:0.17.6" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/linux-ia32@npm:0.17.5" +"@esbuild/linux-ia32@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/linux-ia32@npm:0.17.6" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -9268,86 +9315,86 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/linux-loong64@npm:0.17.5" +"@esbuild/linux-loong64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/linux-loong64@npm:0.17.6" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/linux-mips64el@npm:0.17.5" +"@esbuild/linux-mips64el@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/linux-mips64el@npm:0.17.6" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/linux-ppc64@npm:0.17.5" +"@esbuild/linux-ppc64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/linux-ppc64@npm:0.17.6" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/linux-riscv64@npm:0.17.5" +"@esbuild/linux-riscv64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/linux-riscv64@npm:0.17.6" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/linux-s390x@npm:0.17.5" +"@esbuild/linux-s390x@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/linux-s390x@npm:0.17.6" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/linux-x64@npm:0.17.5" +"@esbuild/linux-x64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/linux-x64@npm:0.17.6" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/netbsd-x64@npm:0.17.5" +"@esbuild/netbsd-x64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/netbsd-x64@npm:0.17.6" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/openbsd-x64@npm:0.17.5" +"@esbuild/openbsd-x64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/openbsd-x64@npm:0.17.6" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/sunos-x64@npm:0.17.5" +"@esbuild/sunos-x64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/sunos-x64@npm:0.17.6" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/win32-arm64@npm:0.17.5" +"@esbuild/win32-arm64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/win32-arm64@npm:0.17.6" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/win32-ia32@npm:0.17.5" +"@esbuild/win32-ia32@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/win32-ia32@npm:0.17.6" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.17.5": - version: 0.17.5 - resolution: "@esbuild/win32-x64@npm:0.17.5" +"@esbuild/win32-x64@npm:0.17.6": + version: 0.17.6 + resolution: "@esbuild/win32-x64@npm:0.17.6" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -9428,6 +9475,30 @@ __metadata: languageName: node linkType: hard +"@frsource/base64@npm:1.0.5": + version: 1.0.5 + resolution: "@frsource/base64@npm:1.0.5" + checksum: ba526dd81df4e91b0abbcc34fc6818c8ff132e5230c23a4448a365ba934f782a86abb2a8566050f9450905851e467a7160f01f3d321fbb0fa3c8c516c5a82b9b + languageName: node + linkType: hard + +"@frsource/cypress-plugin-visual-regression-diff@npm:^3.2.8": + version: 3.2.8 + resolution: "@frsource/cypress-plugin-visual-regression-diff@npm:3.2.8" + dependencies: + "@frsource/base64": 1.0.5 + glob: 8.0.3 + meta-png: 1.0.5 + move-file: 2.1.0 + pixelmatch: 5.3.0 + pngjs: 6.0.0 + sharp: 0.31.2 + peerDependencies: + cypress: ">=4.5.0" + checksum: 7e5a1daf3a36ec7f62ea643da8eff73aadb595e4fb8b400abec37e38a356a200de1ce47ff2e14666e71556300e4d044d5fa5ad21bdd2d214b93402addb1a490e + languageName: node + linkType: hard + "@gar/promisify@npm:^1.0.1, @gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -9531,8 +9602,8 @@ __metadata: linkType: hard "@google-cloud/storage@npm:^6.0.0": - version: 6.9.1 - resolution: "@google-cloud/storage@npm:6.9.1" + version: 6.9.2 + resolution: "@google-cloud/storage@npm:6.9.2" dependencies: "@google-cloud/paginator": ^3.0.7 "@google-cloud/projectify": ^3.0.0 @@ -9551,7 +9622,7 @@ __metadata: retry-request: ^5.0.0 teeny-request: ^8.0.0 uuid: ^8.0.0 - checksum: 9967bc0e80d1531f91030d27fc46cef0242f70dde2064b051104d8d261f1d3cba38b51789b0ae0850bf99b13d093365f85c85c43a2d9468d81f0f98238f76e78 + checksum: e4962be29ca3dd722019db95f99b095993534915d663a82b41c95beaf284d878de5b1357102441e4e375de838299f6d82eff55a2c4e9f610f9a12feac21e2735 languageName: node linkType: hard @@ -10042,7 +10113,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/merge@npm:8.3.16, @graphql-tools/merge@npm:^8.2.6": +"@graphql-tools/merge@npm:8.3.16": version: 8.3.16 resolution: "@graphql-tools/merge@npm:8.3.16" dependencies: @@ -10054,6 +10125,18 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/merge@npm:8.3.18, @graphql-tools/merge@npm:^8.2.6": + version: 8.3.18 + resolution: "@graphql-tools/merge@npm:8.3.18" + dependencies: + "@graphql-tools/utils": 9.2.1 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 0834fcf13dec5ee92e39f2e1b27a098654e2db20bf6ef6f43d7efce56b7d750caa59dd16aacb211b8f5a198d29c19d36471401762f5a34e0bc22657285dcae92 + languageName: node + linkType: hard + "@graphql-tools/mock@npm:^8.1.2": version: 8.6.8 resolution: "@graphql-tools/mock@npm:8.6.8" @@ -10163,7 +10246,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/schema@npm:9.0.14, @graphql-tools/schema@npm:^9.0.0": +"@graphql-tools/schema@npm:9.0.14": version: 9.0.14 resolution: "@graphql-tools/schema@npm:9.0.14" dependencies: @@ -10177,6 +10260,20 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/schema@npm:^9.0.0": + version: 9.0.16 + resolution: "@graphql-tools/schema@npm:9.0.16" + dependencies: + "@graphql-tools/merge": 8.3.18 + "@graphql-tools/utils": 9.2.1 + tslib: ^2.4.0 + value-or-promise: 1.0.12 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: ecbd27b4a36424c8d4b2206b1f2d2ec2cff3c66f52ec452b17977642017ca7f00df4df44f2d0426174ceea9596474d0656bc0227f4c1149f2288abb6d186cd68 + languageName: node + linkType: hard + "@graphql-tools/url-loader@npm:7.16.28, @graphql-tools/url-loader@npm:^7.13.2, @graphql-tools/url-loader@npm:^7.9.7": version: 7.16.28 resolution: "@graphql-tools/url-loader@npm:7.16.28" @@ -10255,7 +10352,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:9.1.4, @graphql-tools/utils@npm:^9.0.0, @graphql-tools/utils@npm:^9.1.1": +"@graphql-tools/utils@npm:9.1.4": version: 9.1.4 resolution: "@graphql-tools/utils@npm:9.1.4" dependencies: @@ -10266,6 +10363,18 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/utils@npm:9.2.1, @graphql-tools/utils@npm:^9.0.0, @graphql-tools/utils@npm:^9.1.1": + version: 9.2.1 + resolution: "@graphql-tools/utils@npm:9.2.1" + dependencies: + "@graphql-typed-document-node/core": ^3.1.1 + tslib: ^2.4.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 94ed12df5f49e5c338322ffd931236a687a3d5c443bf499f9baab5d4fcd9792234111142be8aa506a01ca2e82732996c4e1d8f6159ff9cc7fdc5c97f63e55226 + languageName: node + linkType: hard + "@graphql-tools/wrap@npm:9.2.23": version: 9.2.23 resolution: "@graphql-tools/wrap@npm:9.2.23" @@ -10794,460 +10903,6 @@ __metadata: languageName: node linkType: hard -"@jimp/bmp@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/bmp@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - bmp-js: ^0.1.0 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 3a5a7600add0edf68a0d8f8397dd0ca37fa20d93b4d9ea8321875acd92e4a632a31ac1af0fc2129cff76cdc77aa77b359327613f822c0bc48781f007459e44f8 - languageName: node - linkType: hard - -"@jimp/core@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/core@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - any-base: ^1.1.0 - buffer: ^5.2.0 - core-js: ^3.4.1 - exif-parser: ^0.1.12 - file-type: ^9.0.0 - load-bmfont: ^1.3.1 - mkdirp: ^0.5.1 - phin: ^2.9.1 - pixelmatch: ^4.0.2 - tinycolor2: ^1.4.1 - checksum: f67db147a114b7a220621b80f9c65cf0d1434f4bd7ab8203b44246b79093f9ec46170a6205755eae7fe53c14d2935d8565f5a2185ccd4495ae1cd6f13bcf4852 - languageName: node - linkType: hard - -"@jimp/custom@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/custom@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/core": ^0.10.3 - core-js: ^3.4.1 - checksum: 8a282c9387cb975a533d140a8e0c9f7c23b12b6e6a92e9a1e979b7cc7d89e623189c47b0d0119aa7e497149f0d9a85e4ec14a605921527340c307b7750ba519a - languageName: node - linkType: hard - -"@jimp/gif@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/gif@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - omggif: ^1.0.9 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: a23253e6762e6f19b0fa6deaa9db3bca25b313eb5ea506dd3d844407598d86cf4188cc92c091019a4257450821ffe24f0475cc2e4a1ef24998d95d176078cf2f - languageName: node - linkType: hard - -"@jimp/jpeg@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/jpeg@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - jpeg-js: ^0.3.4 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: cb830ddf3fddbc4f833086ffbf8f21e0c73863522cce86d6f6c82d4f0183d0619ee280736c5ed07563bfcf9f884fd96bdb845a95c5a8a6e11f961f4e95cb4b24 - languageName: node - linkType: hard - -"@jimp/plugin-blit@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-blit@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 5858557359eb0405c29741c8ffe98b4bb26b42564c178402e21b74733c02e04233d2a1af095cff4cd2864ab43d79e01033559f250b3eb879600138007a7d7453 - languageName: node - linkType: hard - -"@jimp/plugin-blur@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-blur@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: d3a2e14d4dc5f3b5ad010888e70193cc8f7327aeda67989eba3f49de2e5324fa6adf89e3a7f1597343f59688ce6e299ad5818aa6c670ed8596ddfba0ca281db3 - languageName: node - linkType: hard - -"@jimp/plugin-circle@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-circle@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: d351c85f4a9aeb404302a03f5d99e6486b95f271f2c628758c8e3d1574bc268278ffc34d9d00d3221d6079431b06a5e6a5a3922863994000b5eb3c81f8c27d35 - languageName: node - linkType: hard - -"@jimp/plugin-color@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-color@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - tinycolor2: ^1.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 2014802f3b0465abbd7682336836183293a26315acbb84826f5207a374f807c574e120790738dcf51aba2cb7e9a481003289ef8bf429facb9fe36a19ce5b6924 - languageName: node - linkType: hard - -"@jimp/plugin-contain@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-contain@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - "@jimp/plugin-blit": ">=0.3.5" - "@jimp/plugin-resize": ">=0.3.5" - "@jimp/plugin-scale": ">=0.3.5" - checksum: 91d77621a350a733839fa8bc15880bc74ac666682a651e676797207dc4f55cba465f3695f6280f3d05122d67067a0a94f3905f323410d880484dd769055c9b49 - languageName: node - linkType: hard - -"@jimp/plugin-cover@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-cover@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - "@jimp/plugin-crop": ">=0.3.5" - "@jimp/plugin-resize": ">=0.3.5" - "@jimp/plugin-scale": ">=0.3.5" - checksum: ca664509c2d42e987a56e80fa848ec3e5590a1f919c0a607d7a03b8192571456c0287a2f768785bf816ecfe7889a7a786490a408866acc94ce5306b39ca82f6b - languageName: node - linkType: hard - -"@jimp/plugin-crop@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-crop@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 54e79b6fa831f813f28dac53187dfa1684d00ece8a98433c30c1e61333ba2b5373758889d202418a55c99dffe235daf153454f35691324b42dd9a103a65d3bd2 - languageName: node - linkType: hard - -"@jimp/plugin-displace@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-displace@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 17b5792ce07ac3b7b5aa8c457626fb46ded0a8f05027c4580fac1b22428e7c2c0ff04e0ad4f7249c179322afa7921e57afc88ace03e2d08b67e864c24ef5c060 - languageName: node - linkType: hard - -"@jimp/plugin-dither@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-dither@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 77caaceebccceb73e32616fd79517d9640d217403577715e08146bf2a67599822070be8a51783ad76402e0303f6bc9afcebf75f75cc73ae44c684e3c1e1248ea - languageName: node - linkType: hard - -"@jimp/plugin-fisheye@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-fisheye@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: f60d9c6c40d6fa1b85658ffa23568f95aeab1635165f2b74266b128be0f9b4bf963cfa755e5a43b7f18421340a4bb7b628ab75821b34b602924154b55c0f39ce - languageName: node - linkType: hard - -"@jimp/plugin-flip@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-flip@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - "@jimp/plugin-rotate": ">=0.3.5" - checksum: 6542b9e595e3b95cf8ac741226e9089ab1edd3fa19bc4ac159afd5bc3801a55f6ee93ade029626dbde7977eb1fbca51c71d9c6546b5285754eb29b54a7f5f5ff - languageName: node - linkType: hard - -"@jimp/plugin-gaussian@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-gaussian@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 465ca178b9a9062eb6db4a57fe3b47236270bad9600cfb4aae162aad43f20e5cf90c19bf77bd08867f25abd0679c6018412e0ad8cb3777640dad5c3f51e64dd9 - languageName: node - linkType: hard - -"@jimp/plugin-invert@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-invert@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 917419301a961541466e04c7fc974fc95f35e3f95f75e6bfef141270f06b68bcdf61321cad2d545cd8115a6e3a99992345f964bdb94cbdb48aaf42885b8fcae7 - languageName: node - linkType: hard - -"@jimp/plugin-mask@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-mask@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 7870fe46d9fcec6145476bcceed23568012e87953772e7ea2b82c8ed48375ee525db6b977816574b47e08c595bd6123c8d9cd24a95e649677d0ec1fada4e6831 - languageName: node - linkType: hard - -"@jimp/plugin-normalize@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-normalize@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: f34ebf3ef03d83b2cb9a8779d39a3e10707aeae4dfe67e1bb30202589e72d09c0e9a61b429de4e4886728f0faabbb83a6ff081f2e9bf79b517e2ea387c53a9a4 - languageName: node - linkType: hard - -"@jimp/plugin-print@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-print@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - load-bmfont: ^1.4.0 - peerDependencies: - "@jimp/custom": ">=0.3.5" - "@jimp/plugin-blit": ">=0.3.5" - checksum: 772a7d82741af583293006f699bbbaf116c264327412ea6ed0f3b7fb14d0dcbef51278e6fce3e966ad23e655114bfe949106d551d06459819b26e461638e8a5e - languageName: node - linkType: hard - -"@jimp/plugin-resize@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-resize@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: aff691d2936bd88d59a68b36a6f06b306d0f7c654baec7b43371e6b250397b3a19d0b98a620ad26738ed51357f5899a8002761d8f6f0ae6074e6c63905895a5d - languageName: node - linkType: hard - -"@jimp/plugin-rotate@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-rotate@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - "@jimp/plugin-blit": ">=0.3.5" - "@jimp/plugin-crop": ">=0.3.5" - "@jimp/plugin-resize": ">=0.3.5" - checksum: f1771365753a64387e455006467dd41e09c1e329180e57df77ef78f72dfac7d889e5d03adae1038f4c6ac081a9d5113c59541983fdda6bf08da9af852650c468 - languageName: node - linkType: hard - -"@jimp/plugin-scale@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-scale@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - "@jimp/plugin-resize": ">=0.3.5" - checksum: 356e8ab040bf842845a29cc0a6d7170fd15c1a9e9407028b88771bff01ecb6870c8f26d15b59b432914c430e611f36f54f932aaa7a68dcc9e32efae2f6dd1946 - languageName: node - linkType: hard - -"@jimp/plugin-shadow@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-shadow@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - "@jimp/plugin-blur": ">=0.3.5" - "@jimp/plugin-resize": ">=0.3.5" - checksum: 7f07387160b31732b3d43a2c4d44f8716be7a08b2f0fcfd58d18ca76ed62d825629c6273434bd23d02ade6a18bc8d7c7020eb9ab0ccc95acd85e718b5ab2c7a0 - languageName: node - linkType: hard - -"@jimp/plugin-threshold@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugin-threshold@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - "@jimp/plugin-color": ">=0.8.0" - "@jimp/plugin-resize": ">=0.8.0" - checksum: 550de37f82ccfaacb954b0e183a7bfa77f80f33ec9f5b08756ce778675284e486a2b5860c69c6d15a38d1d7030f1896e1ac79e8675603ead9e36c075f3cf636e - languageName: node - linkType: hard - -"@jimp/plugins@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/plugins@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/plugin-blit": ^0.10.3 - "@jimp/plugin-blur": ^0.10.3 - "@jimp/plugin-circle": ^0.10.3 - "@jimp/plugin-color": ^0.10.3 - "@jimp/plugin-contain": ^0.10.3 - "@jimp/plugin-cover": ^0.10.3 - "@jimp/plugin-crop": ^0.10.3 - "@jimp/plugin-displace": ^0.10.3 - "@jimp/plugin-dither": ^0.10.3 - "@jimp/plugin-fisheye": ^0.10.3 - "@jimp/plugin-flip": ^0.10.3 - "@jimp/plugin-gaussian": ^0.10.3 - "@jimp/plugin-invert": ^0.10.3 - "@jimp/plugin-mask": ^0.10.3 - "@jimp/plugin-normalize": ^0.10.3 - "@jimp/plugin-print": ^0.10.3 - "@jimp/plugin-resize": ^0.10.3 - "@jimp/plugin-rotate": ^0.10.3 - "@jimp/plugin-scale": ^0.10.3 - "@jimp/plugin-shadow": ^0.10.3 - "@jimp/plugin-threshold": ^0.10.3 - core-js: ^3.4.1 - timm: ^1.6.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 28833f4ce43f911c41b5c251759f5e38406ba99249a7c00d11a81e4ce58027eb993ca0c220601c3702832c2855dbb0554762bf724107bb4fdd0a2c1cf8753895 - languageName: node - linkType: hard - -"@jimp/png@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/png@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/utils": ^0.10.3 - core-js: ^3.4.1 - pngjs: ^3.3.3 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 1520b669d0dc6a0b47d7357c5e75a080e3a56db0459fad5c56e299b03c4a7bf560540480b08fa2e1eaf283ba153efb26a3e90d90fe373069c6c6184f7ea3e163 - languageName: node - linkType: hard - -"@jimp/tiff@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/tiff@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - core-js: ^3.4.1 - utif: ^2.0.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 6ff41bc962afdbcd223b331beed232b5aecf2b0ba06cccddaef5b5d4b925fa51d6a26d226080baabd25d7dceb0b9b0e5c69b60c44494822cb69988c3221c6514 - languageName: node - linkType: hard - -"@jimp/types@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/types@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/bmp": ^0.10.3 - "@jimp/gif": ^0.10.3 - "@jimp/jpeg": ^0.10.3 - "@jimp/png": ^0.10.3 - "@jimp/tiff": ^0.10.3 - core-js: ^3.4.1 - timm: ^1.6.1 - peerDependencies: - "@jimp/custom": ">=0.3.5" - checksum: 68ea0f1a1f3116ba779c898c9fb38fb7b822addaa14ac153aea4cdc9e274efc34a46a6ad4c8a9978fb88b4ab5a826190e28b89b431dce735ee760997f2d66678 - languageName: node - linkType: hard - -"@jimp/utils@npm:^0.10.3": - version: 0.10.3 - resolution: "@jimp/utils@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - core-js: ^3.4.1 - regenerator-runtime: ^0.13.3 - checksum: bd36b8bbaaf878190afe357f1bcebed9215dae503f06603039e6a106045588003b845150cb5a5ba6dd4d973e7e7b95779158ae921011fb6bd79083c8cb177804 - languageName: node - linkType: hard - "@josephg/resolvable@npm:^1.0.0": version: 1.0.1 resolution: "@josephg/resolvable@npm:1.0.1" @@ -13831,6 +13486,7 @@ __metadata: "@backstage/cli-common": "workspace:^" "@backstage/config": "workspace:^" "@backstage/plugin-techdocs-node": "workspace:^" + "@frsource/cypress-plugin-visual-regression-diff": ^3.2.8 "@types/commander": ^2.12.2 "@types/dockerode": ^3.3.0 "@types/fs-extra": ^9.0.6 @@ -13840,7 +13496,6 @@ __metadata: "@types/webpack-env": ^1.15.3 commander: ^9.1.0 cypress: ^10.0.0 - cypress-plugin-snapshots: ^1.4.4 dockerode: ^3.3.1 find-process: ^1.4.5 fs-extra: ^10.0.1 @@ -14330,6 +13985,15 @@ __metadata: languageName: node linkType: hard +"@types/cross-spawn@npm:^6.0.2": + version: 6.0.2 + resolution: "@types/cross-spawn@npm:6.0.2" + dependencies: + "@types/node": "*" + checksum: fa9edd32178878cab3ea8d6d0260639e0fe4860ddb3887b8de53d6e8036e154fc5f313c653f690975aa25025aea8beb83fb0870b931bf8d9202c3ac530a24c9d + languageName: node + linkType: hard + "@types/d3-color@npm:*": version: 3.0.2 resolution: "@types/d3-color@npm:3.0.2" @@ -14895,7 +14559,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.11, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d @@ -15136,9 +14800,9 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>= 8, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0, @types/node@npm:^18.11.17": - version: 18.11.18 - resolution: "@types/node@npm:18.11.18" - checksum: 03f17f9480f8d775c8a72da5ea7e9383db5f6d85aa5fefde90dd953a1449bd5e4ffde376f139da4f3744b4c83942166d2a7603969a6f8ea826edfb16e6e3b49d + version: 18.11.19 + resolution: "@types/node@npm:18.11.19" + checksum: d7cd19fcfc59cbdd3f9ba0b4072cb7adc21bd575bd8eb7d7e698975e63564aaa83f03434f32b12331f84f73d0b369d9cbe2371e359d9d7f5c3361f4987f4f7da languageName: node linkType: hard @@ -15178,9 +14842,9 @@ __metadata: linkType: hard "@types/node@npm:^16.0.0, @types/node@npm:^16.11.26, @types/node@npm:^16.9.2": - version: 16.18.11 - resolution: "@types/node@npm:16.18.11" - checksum: 2a3b1da13063debe6e26f732defb5f03ef4ef732c3e08daba838d8850433bd00e537ce1a97ce9bcfc4b15db5218d701d1265fae94e0d6926906bec157e6b46e0 + version: 16.18.12 + resolution: "@types/node@npm:16.18.12" + checksum: fc3271182414f8593018ef8f00b4718116a92f463f619081bd399d9460e7861e1dd7eebc7cf94c23567e418ff397babed077011711aae8d47171b5a81c5bd71d languageName: node linkType: hard @@ -16573,13 +16237,6 @@ __metadata: languageName: node linkType: hard -"after@npm:0.8.2": - version: 0.8.2 - resolution: "after@npm:0.8.2" - checksum: 52ea9be2e97d764de256dfb3843d68cb180d2d464748010ea0d8079ee28190190080a53e4005a62adbf4337c2d613906db82f08c0091cccb8e817625ccf94c52 - languageName: node - linkType: hard - "agent-base@npm:6, agent-base@npm:^6.0.2": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -16776,13 +16433,6 @@ __metadata: languageName: node linkType: hard -"any-base@npm:^1.1.0": - version: 1.1.0 - resolution: "any-base@npm:1.1.0" - checksum: c1fd040de52e710e2de7d9ae4df52bac589f35622adb24686c98ce21c7b824859a8db9614bc119ed8614f42fd08918b2612e6a6c385480462b3100a1af59289d - languageName: node - linkType: hard - "any-promise@npm:^1.0.0": version: 1.3.0 resolution: "any-promise@npm:1.3.0" @@ -17100,15 +16750,6 @@ __metadata: languageName: node linkType: hard -"array-union@npm:^1.0.1": - version: 1.0.2 - resolution: "array-union@npm:1.0.2" - dependencies: - array-uniq: ^1.0.1 - checksum: 82cec6421b6e6766556c484835a6d476a873f1b71cace5ab2b4f1b15b1e3162dc4da0d16f7a2b04d4aec18146c6638fe8f661340b31ba8e469fd811a1b45dc8d - languageName: node - linkType: hard - "array-union@npm:^2.1.0": version: 2.1.0 resolution: "array-union@npm:2.1.0" @@ -17116,13 +16757,6 @@ __metadata: languageName: node linkType: hard -"array-uniq@npm:^1.0.1": - version: 1.0.3 - resolution: "array-uniq@npm:1.0.3" - checksum: 1625f06b093d8bf279b81adfec6e72951c0857d65b5e3f65f053fffe9f9dd61c2fc52cff57e38a4700817e7e3f01a4faa433d505ea9e33cdae4514c334e0bf9e - languageName: node - linkType: hard - "array.prototype.flat@npm:^1.2.3, array.prototype.flat@npm:^1.3.1": version: 1.3.1 resolution: "array.prototype.flat@npm:1.3.1" @@ -17160,13 +16794,6 @@ __metadata: languageName: node linkType: hard -"arraybuffer.slice@npm:~0.0.7": - version: 0.0.7 - resolution: "arraybuffer.slice@npm:0.0.7" - checksum: c6bacada71e8fe3e63c5f5ac5f23810e617faa1bcf33be21db43f40819edc7e186172d4a6fb8f1b9baea26689fc8da6ddeb78cefa2cc9723156c0247c3587118 - languageName: node - linkType: hard - "arrify@npm:^1.0.1": version: 1.0.1 resolution: "arrify@npm:1.0.1" @@ -17335,6 +16962,13 @@ __metadata: languageName: node linkType: hard +"avsc@npm:^5.7.3": + version: 5.7.7 + resolution: "avsc@npm:5.7.7" + checksum: e3361aa88a61397b3345876263f79c8c8bfe013d849142202758205459a37e24cdbf02edc49ae019d6e82d93bbc7bc73e9e7fefca049aae91626bae28de4d1a9 + languageName: node + linkType: hard + "aws-lambda@npm:^1.0.7": version: 1.0.7 resolution: "aws-lambda@npm:1.0.7" @@ -17712,7 +17346,7 @@ __metadata: languageName: node linkType: hard -"backo2@npm:1.0.2, backo2@npm:^1.0.2": +"backo2@npm:^1.0.2": version: 1.0.2 resolution: "backo2@npm:1.0.2" checksum: fda8d0a0f4810068d23715f2f45153146d6ee8f62dd827ce1e0b6cc3c8328e84ad61e11399a83931705cef702fe7cbb457856bf99b9bd10c4ed57b0786252385 @@ -17754,13 +17388,6 @@ __metadata: languageName: node linkType: hard -"base64-arraybuffer@npm:0.1.4": - version: 0.1.4 - resolution: "base64-arraybuffer@npm:0.1.4" - checksum: d249a929e27b2430d7ba1527e91a36e14da37ae2f80e350c4d696a038257718f8da07577e820e7262f86a0ecd573c283db10c46502080f53ae11bfdd99b6a029 - languageName: node - linkType: hard - "base64-js@npm:^1.0.2, base64-js@npm:^1.3.0, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -17775,13 +17402,6 @@ __metadata: languageName: node linkType: hard -"base64id@npm:2.0.0": - version: 2.0.0 - resolution: "base64id@npm:2.0.0" - checksum: 581b1d37e6cf3738b7ccdd4d14fe2bfc5c238e696e2720ee6c44c183b838655842e22034e53ffd783f872a539915c51b0d4728a49c7cc678ac5a758e00d62168 - languageName: node - linkType: hard - "base64url@npm:3.x.x": version: 3.0.1 resolution: "base64url@npm:3.0.1" @@ -17943,13 +17563,6 @@ __metadata: languageName: node linkType: hard -"blob@npm:0.0.5": - version: 0.0.5 - resolution: "blob@npm:0.0.5" - checksum: ca6a025f1108b7fd317fba0a0d64424f5cf47be6f4653ec8ba44777804a4e5c86c8d7a7e41f75fe52fd77c5bfc3479f68e017abe649a241c15c3fc07bfc59e7b - languageName: node - linkType: hard - "bluebird@npm:3.7.2, bluebird@npm:^3.3.5, bluebird@npm:^3.5.5, bluebird@npm:^3.7.2": version: 3.7.2 resolution: "bluebird@npm:3.7.2" @@ -17957,13 +17570,6 @@ __metadata: languageName: node linkType: hard -"bmp-js@npm:^0.1.0": - version: 0.1.0 - resolution: "bmp-js@npm:0.1.0" - checksum: 2f6cf7eeabae2aa50eb768122f59e9752caa97248028cb8b5cf0d9db7cf8fb3a60262aeb2c6889dd21357ab061b2fb318f21f20d2b24963ba36ead8e264c6654 - languageName: node - linkType: hard - "bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.1.1, bn.js@npm:^4.11.9": version: 4.12.0 resolution: "bn.js@npm:4.12.0" @@ -18211,13 +17817,6 @@ __metadata: languageName: node linkType: hard -"buffer-equal@npm:0.0.1": - version: 0.0.1 - resolution: "buffer-equal@npm:0.0.1" - checksum: ca4b52e6c01143529d957a78cb9a93e4257f172bbab30d9eb87c20ae085ed23c5e07f236ac051202dacbf3d17aba42e1455f84cba21ea79b67d57f2b05e9a613 - languageName: node - linkType: hard - "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -18260,7 +17859,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.2.0, buffer@npm:^5.2.1, buffer@npm:^5.5.0, buffer@npm:^5.6.0": +"buffer@npm:^5.2.1, buffer@npm:^5.5.0, buffer@npm:^5.6.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -19187,7 +18786,7 @@ __metadata: languageName: node linkType: hard -"color@npm:^4.0.1": +"color@npm:^4.0.1, color@npm:^4.2.3": version: 4.2.3 resolution: "color@npm:4.2.3" dependencies: @@ -19377,34 +18976,13 @@ __metadata: languageName: node linkType: hard -"component-bind@npm:1.0.0": - version: 1.0.0 - resolution: "component-bind@npm:1.0.0" - checksum: 746c5810b9f8735643840ad04072e1ab817444d44dc1aadc813f1f1a17c47c27616584caa0db93db7e687bfe73b65073d8246c785bcdac80f8f3627d3bb26883 - languageName: node - linkType: hard - -"component-emitter@npm:1.2.1": - version: 1.2.1 - resolution: "component-emitter@npm:1.2.1" - checksum: 00599b827635cab65bb20e5e3e2db4cea120b76b6626ce3ac6c85d7f5f39bbadd9fec530da444380035dd1c8ff08f9badca54d40b68feaf74bc64f75d537ef61 - languageName: node - linkType: hard - -"component-emitter@npm:^1.3.0, component-emitter@npm:~1.3.0": +"component-emitter@npm:^1.3.0": version: 1.3.0 resolution: "component-emitter@npm:1.3.0" checksum: b3c46de38ffd35c57d1c02488355be9f218e582aec72d72d1b8bbec95a3ac1b38c96cd6e03ff015577e68f550fbb361a3bfdbd9bb248be9390b7b3745691be6b languageName: node linkType: hard -"component-inherit@npm:0.0.3": - version: 0.0.3 - resolution: "component-inherit@npm:0.0.3" - checksum: 9f5b872a6b3a396cf827d471d07db5626d7918202ab6c1d59f2b849bab18ce4fe61d9b2b7b6ae5cd547b81f27a3de5ae678f438bfe4fadce8f26a1526ba86a74 - languageName: node - linkType: hard - "compress-commons@npm:^4.1.0": version: 4.1.0 resolution: "compress-commons@npm:4.1.0" @@ -19648,7 +19226,7 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.4.2, cookie@npm:^0.4.2, cookie@npm:~0.4.1": +"cookie@npm:0.4.2, cookie@npm:^0.4.2": version: 0.4.2 resolution: "cookie@npm:0.4.2" checksum: a00833c998bedf8e787b4c342defe5fa419abd96b32f4464f718b91022586b8f1bafbddd499288e75c037642493c83083da426c6a9080d309e3bd90fd11baa9b @@ -19709,7 +19287,7 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^3.4.1, core-js@npm:^3.6.5": +"core-js@npm:^3.6.5": version: 3.27.2 resolution: "core-js@npm:3.27.2" checksum: 718debd426f55a6b97cf9b757c936be258afd6d4f7052f89d0f96c982d7013e9000b0b006df42831a0cf32adad298e34d6a19052dce9ae1c7ab87162c0c665e0 @@ -20263,31 +19841,6 @@ __metadata: languageName: node linkType: hard -"cypress-plugin-snapshots@npm:^1.4.4": - version: 1.4.4 - resolution: "cypress-plugin-snapshots@npm:1.4.4" - dependencies: - diff2html: ^2.7.0 - fs-extra: ^7.0.1 - image-size: ^0.7.2 - jimp: ^0.10.3 - js-base64: ^2.5.1 - lodash: ^4.17.13 - pixelmatch: ^4.0.2 - pngjs: ^3.3.3 - prettier: ^1.16.4 - rimraf: ^2.6.3 - sanitize-filename: ^1.6.1 - socket.io: ^2.2.0 - socket.io-client: ^2.2.0 - source-map-support: ^0.5.10 - unidiff: 1.0.2 - peerDependencies: - cypress: ^4.5.0 - checksum: 75b8d6b6e0353f7d2ac617c503f33c84c7c5f4e650dbaf59a76558e971bcc82532d2ac37b9eeccb7382354a13fe5d0068c8e97a9f7f6f1af6135cf5286c285f9 - languageName: node - linkType: hard - "cypress@npm:^10.0.0": version: 10.11.0 resolution: "cypress@npm:10.11.0" @@ -20655,24 +20208,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:~3.1.0": - version: 3.1.0 - resolution: "debug@npm:3.1.0" - dependencies: - ms: 2.0.0 - checksum: 0b52718ab957254a5b3ca07fc34543bc778f358620c206a08452251eb7fc193c3ea3505072acbf4350219c14e2d71ceb7bdaa0d3370aa630b50da790458d08b3 - languageName: node - linkType: hard - -"debug@npm:~4.1.0": - version: 4.1.1 - resolution: "debug@npm:4.1.1" - dependencies: - ms: ^2.1.1 - checksum: 1e681f5cce94ba10f8dde74b20b42e4d8cf0d2a6700f4c165bb3bb6885565ef5ca5885bf07e704974a835f2415ff095a63164f539988a1f07e8a69fe8b1d65ad - languageName: node - linkType: hard - "debuglog@npm:^1.0.1": version: 1.0.1 resolution: "debuglog@npm:1.0.1" @@ -20966,7 +20501,7 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.0": +"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1": version: 2.0.1 resolution: "detect-libc@npm:2.0.1" checksum: ccb05fcabbb555beb544d48080179c18523a343face9ee4e1a86605a8715b4169f94d663c21a03c310ac824592f2ba9a5270218819bb411ad7be578a527593d7 @@ -21031,18 +20566,6 @@ __metadata: languageName: node linkType: hard -"diff2html@npm:^2.7.0": - version: 2.12.2 - resolution: "diff2html@npm:2.12.2" - dependencies: - diff: ^4.0.1 - hogan.js: ^3.0.2 - merge: ^1.2.1 - whatwg-fetch: ^3.0.0 - checksum: 4d659fb558575406881bc9e6b892a236d038bca1335cbdde65ebfde462cf1cff7a7ab4a2e82d9874205e658dd2caa31699ba24ea4efb714d7c7277ddb96c7768 - languageName: node - linkType: hard - "diff3@npm:0.0.3": version: 0.0.3 resolution: "diff3@npm:0.0.3" @@ -21050,13 +20573,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:^2.2.2": - version: 2.2.3 - resolution: "diff@npm:2.2.3" - checksum: b5b6fbe1848c621ef6f6c154e689842f7bb992b4c0ae257b70f5577b0906812537bf2eccda14d0215f90579567c8880eb44f1cf3851bd615583214a4f4d61ce3 - languageName: node - linkType: hard - "diff@npm:^4.0.1": version: 4.0.2 resolution: "diff@npm:4.0.2" @@ -21082,15 +20598,6 @@ __metadata: languageName: node linkType: hard -"dir-glob@npm:^2.0.0": - version: 2.2.2 - resolution: "dir-glob@npm:2.2.2" - dependencies: - path-type: ^3.0.0 - checksum: 3aa48714a9f7845ffc30ab03a5c674fe760477cc55e67b0847333371549227d93953e6627ec160f75140c5bea5c5f88d13c01de79bd1997a588efbcf06980842 - languageName: node - linkType: hard - "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -21223,13 +20730,6 @@ __metadata: languageName: node linkType: hard -"dom-walk@npm:^0.1.0": - version: 0.1.1 - resolution: "dom-walk@npm:0.1.1" - checksum: f55db10d0344b87cca1046a2356915a8e9ddee90fc97d3ebb26c4021e593cce47d24cbef18aac721421787c6a60fa14dd079a0b81f8610c9aeeec52177fd2acf - languageName: node - linkType: hard - "domain-browser@npm:^1.1.1": version: 1.2.0 resolution: "domain-browser@npm:1.2.0" @@ -21554,52 +21054,6 @@ __metadata: languageName: node linkType: hard -"engine.io-client@npm:~3.5.0": - version: 3.5.2 - resolution: "engine.io-client@npm:3.5.2" - dependencies: - component-emitter: ~1.3.0 - component-inherit: 0.0.3 - debug: ~3.1.0 - engine.io-parser: ~2.2.0 - has-cors: 1.1.0 - indexof: 0.0.1 - parseqs: 0.0.6 - parseuri: 0.0.6 - ws: ~7.4.2 - xmlhttprequest-ssl: ~1.6.2 - yeast: 0.1.2 - checksum: 2a4a8407ea939c45826d3483e39fe017e66d488dfd101282a79644f75196f7b84cac9d6f981e3ace1ab231e2469c18b25d79b114f20ab58d0eaa393bf66f213e - languageName: node - linkType: hard - -"engine.io-parser@npm:~2.2.0": - version: 2.2.1 - resolution: "engine.io-parser@npm:2.2.1" - dependencies: - after: 0.8.2 - arraybuffer.slice: ~0.0.7 - base64-arraybuffer: 0.1.4 - blob: 0.0.5 - has-binary2: ~1.0.2 - checksum: c7291955c1af4b6c384c2642c2d10a4df0ca7b6acc5cb178dd4933bcb1522eccc3d89ffae6e6080ce760451378fce49db47af3fb9999cae08de310e75f3c29d6 - languageName: node - linkType: hard - -"engine.io@npm:~3.6.0": - version: 3.6.1 - resolution: "engine.io@npm:3.6.1" - dependencies: - accepts: ~1.3.4 - base64id: 2.0.0 - cookie: ~0.4.1 - debug: ~4.1.0 - engine.io-parser: ~2.2.0 - ws: ~7.4.2 - checksum: 7dd9eddc79aedf61a63141e6208ee89936c659445378f965bff6d5eb3e9cb4815aff735f2b1886487e2be51116a343e89d972ae5790bf2ac9fdcb2572baf8935 - languageName: node - linkType: hard - "enhanced-resolve@npm:^5.10.0": version: 5.10.0 resolution: "enhanced-resolve@npm:5.10.0" @@ -21938,7 +21392,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.15.6": +"esbuild@npm:^0.15.6, esbuild@npm:~0.15.10": version: 0.15.18 resolution: "esbuild@npm:0.15.18" dependencies: @@ -22016,31 +21470,31 @@ __metadata: linkType: hard "esbuild@npm:^0.17.0": - version: 0.17.5 - resolution: "esbuild@npm:0.17.5" + version: 0.17.6 + resolution: "esbuild@npm:0.17.6" dependencies: - "@esbuild/android-arm": 0.17.5 - "@esbuild/android-arm64": 0.17.5 - "@esbuild/android-x64": 0.17.5 - "@esbuild/darwin-arm64": 0.17.5 - "@esbuild/darwin-x64": 0.17.5 - "@esbuild/freebsd-arm64": 0.17.5 - "@esbuild/freebsd-x64": 0.17.5 - "@esbuild/linux-arm": 0.17.5 - "@esbuild/linux-arm64": 0.17.5 - "@esbuild/linux-ia32": 0.17.5 - "@esbuild/linux-loong64": 0.17.5 - "@esbuild/linux-mips64el": 0.17.5 - "@esbuild/linux-ppc64": 0.17.5 - "@esbuild/linux-riscv64": 0.17.5 - "@esbuild/linux-s390x": 0.17.5 - "@esbuild/linux-x64": 0.17.5 - "@esbuild/netbsd-x64": 0.17.5 - "@esbuild/openbsd-x64": 0.17.5 - "@esbuild/sunos-x64": 0.17.5 - "@esbuild/win32-arm64": 0.17.5 - "@esbuild/win32-ia32": 0.17.5 - "@esbuild/win32-x64": 0.17.5 + "@esbuild/android-arm": 0.17.6 + "@esbuild/android-arm64": 0.17.6 + "@esbuild/android-x64": 0.17.6 + "@esbuild/darwin-arm64": 0.17.6 + "@esbuild/darwin-x64": 0.17.6 + "@esbuild/freebsd-arm64": 0.17.6 + "@esbuild/freebsd-x64": 0.17.6 + "@esbuild/linux-arm": 0.17.6 + "@esbuild/linux-arm64": 0.17.6 + "@esbuild/linux-ia32": 0.17.6 + "@esbuild/linux-loong64": 0.17.6 + "@esbuild/linux-mips64el": 0.17.6 + "@esbuild/linux-ppc64": 0.17.6 + "@esbuild/linux-riscv64": 0.17.6 + "@esbuild/linux-s390x": 0.17.6 + "@esbuild/linux-x64": 0.17.6 + "@esbuild/netbsd-x64": 0.17.6 + "@esbuild/openbsd-x64": 0.17.6 + "@esbuild/sunos-x64": 0.17.6 + "@esbuild/win32-arm64": 0.17.6 + "@esbuild/win32-ia32": 0.17.6 + "@esbuild/win32-x64": 0.17.6 dependenciesMeta: "@esbuild/android-arm": optional: true @@ -22088,7 +21542,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 31829b46a0cad65f58d90d39b66fd32a2de820539dd8530199975e417aa0ecd63cfc340e402f4f4e9235906a1a05969ddac1d4ad1ba611a3f11c0f62516b9390 + checksum: 13c4c3bcaa7ff291810d2c8294a1eb997b4672c60a9285b32d8eafcbc552e4468e06efe9d1a15067f5cbd41adcb12ddb4362618845e2e916e437f281c9aa80ab languageName: node linkType: hard @@ -22207,7 +21661,7 @@ __metadata: languageName: node linkType: hard -"eslint-module-utils@npm:^2.1.1, eslint-module-utils@npm:^2.7.4": +"eslint-module-utils@npm:^2.7.4": version: 2.7.4 resolution: "eslint-module-utils@npm:2.7.4" dependencies: @@ -22312,21 +21766,6 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-monorepo@npm:^0.3.2": - version: 0.3.2 - resolution: "eslint-plugin-monorepo@npm:0.3.2" - dependencies: - eslint-module-utils: ^2.1.1 - get-monorepo-packages: ^1.1.0 - globby: ^7.1.1 - load-json-file: ^4.0.0 - minimatch: ^3.0.4 - parse-package-name: ^0.1.0 - path-is-inside: ^1.0.2 - checksum: b6f17efbc9e66aefbf7aacf9aa130bc8e729a6311c412d1b0026ce7d93361c10b0e6e4b10cf434f24ca9a8b2e891bc8e5b82e66dc9ccc5e782f9c18a989eca28 - languageName: node - linkType: hard - "eslint-plugin-notice@npm:^0.9.10": version: 0.9.10 resolution: "eslint-plugin-notice@npm:0.9.10" @@ -22446,7 +21885,7 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.6.0": +"eslint@npm:^8.33.0, eslint@npm:^8.6.0": version: 8.33.0 resolution: "eslint@npm:8.33.0" dependencies: @@ -22913,13 +22352,6 @@ __metadata: languageName: node linkType: hard -"exif-parser@npm:^0.1.12": - version: 0.1.12 - resolution: "exif-parser@npm:0.1.12" - checksum: 6ba50cb9e0b45a6efa37e966a9582ecd171b5c5b3ef0c47542f2b862c521f70d2f656dde85b4d2a5dd8e1163486b09049f4c412e9c6176bfbda1654a5b2f021c - languageName: node - linkType: hard - "exit-on-epipe@npm:~1.0.1": version: 1.0.1 resolution: "exit-on-epipe@npm:1.0.1" @@ -23365,13 +22797,6 @@ __metadata: languageName: node linkType: hard -"file-type@npm:^9.0.0": - version: 9.0.0 - resolution: "file-type@npm:9.0.0" - checksum: 9ea78b29c3762d967eb1e3e4f45e401388b6d252b12c217f78f5ea97556ff7e35e4c7255cab68810ac414d51b776bd4e83504c86f132c262a454251561189efa - languageName: node - linkType: hard - "file-uri-to-path@npm:1.0.0": version: 1.0.0 resolution: "file-uri-to-path@npm:1.0.0" @@ -24053,16 +23478,6 @@ __metadata: languageName: node linkType: hard -"get-monorepo-packages@npm:^1.1.0": - version: 1.2.0 - resolution: "get-monorepo-packages@npm:1.2.0" - dependencies: - globby: ^7.1.1 - load-json-file: ^4.0.0 - checksum: f9321c11b8e11f02138758db6589d8ab8b7e1b05e78cac92493b635faea10aa100c20fe40f2cf110c82ec100c118c6c131dff4c65d32a721c617dc2928f7b277 - languageName: node - linkType: hard - "get-package-type@npm:^0.1.0": version: 0.1.0 resolution: "get-package-type@npm:0.1.0" @@ -24110,6 +23525,13 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.4.0": + version: 4.4.0 + resolution: "get-tsconfig@npm:4.4.0" + checksum: e193558b4f0c84c81ae9688cf5b9950dc0b341e44f91b002713fd0c37cfb73108e1cd9998ed540bcc423f193fde32cc58a15e99dd469f5158a2eb4a148611176 + languageName: node + linkType: hard + "getopts@npm:2.3.0": version: 2.3.0 resolution: "getopts@npm:2.3.0" @@ -24200,7 +23622,20 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.0.0, glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7, glob@npm:^7.2.0": +"glob@npm:8.0.3": + version: 8.0.3 + resolution: "glob@npm:8.0.3" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^5.0.1 + once: ^1.3.0 + checksum: 50bcdea19d8e79d8de5f460b1939ffc2b3299eac28deb502093fdca22a78efebc03e66bf54f0abc3d3d07d8134d19a32850288b7440d77e072aa55f9d33b18c5 + languageName: node + linkType: hard + +"glob@npm:^7.0.0, glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7, glob@npm:^7.2.0": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -24270,16 +23705,6 @@ __metadata: languageName: node linkType: hard -"global@npm:~4.4.0": - version: 4.4.0 - resolution: "global@npm:4.4.0" - dependencies: - min-document: ^2.19.0 - process: ^0.11.10 - checksum: 9c057557c8f5a5bcfbeb9378ba4fe2255d04679452be504608dd5f13b54edf79f7be1db1031ea06a4ec6edd3b9f5f17d2d172fb47e6c69dae57fd84b7e72b77f - languageName: node - linkType: hard - "globals@npm:^11.1.0, globals@npm:^11.12.0": version: 11.12.0 resolution: "globals@npm:11.12.0" @@ -24319,20 +23744,6 @@ __metadata: languageName: node linkType: hard -"globby@npm:^7.1.1": - version: 7.1.1 - resolution: "globby@npm:7.1.1" - dependencies: - array-union: ^1.0.1 - dir-glob: ^2.0.0 - glob: ^7.1.2 - ignore: ^3.3.5 - pify: ^3.0.0 - slash: ^1.0.0 - checksum: f0eba08a08ae7c98149a4411661c0bf08c4717d81e6f355cf624fb01880b249737eb8e951bf86124cb3af8ea1c793c0a9d363ed5cdec99bb2c6b68f8a323025f - languageName: node - linkType: hard - "good-listener@npm:^1.2.2": version: 1.2.2 resolution: "good-listener@npm:1.2.2" @@ -24705,22 +24116,6 @@ __metadata: languageName: node linkType: hard -"has-binary2@npm:~1.0.2": - version: 1.0.3 - resolution: "has-binary2@npm:1.0.3" - dependencies: - isarray: 2.0.1 - checksum: 9183a617830b1f50b35961fbe39278cc43089cdac7c47a7f16aafb9d8190553fa360e5da34a479d6c988db40ea10aa02bca5abcb2451b5fc3924ae2f013ffdab - languageName: node - linkType: hard - -"has-cors@npm:1.1.0": - version: 1.1.0 - resolution: "has-cors@npm:1.1.0" - checksum: 549ce94113fd23895b22d71ade9809918577b8558cd4d701fe79045d8b1d58d87eba870260b28f6a3229be933a691c55653afd496d0fc52e98fd2ff577f01197 - languageName: node - linkType: hard - "has-flag@npm:^3.0.0": version: 3.0.0 resolution: "has-flag@npm:3.0.0" @@ -24897,18 +24292,6 @@ __metadata: languageName: node linkType: hard -"hogan.js@npm:^3.0.2": - version: 3.0.2 - resolution: "hogan.js@npm:3.0.2" - dependencies: - mkdirp: 0.3.0 - nopt: 1.0.10 - bin: - hulk: ./bin/hulk - checksum: c7bbff84faa9ca265c39f4a2100546ba0388fcc9c5bac8526f488592ce3fcaa042eba6ac25db277f4478ec3855b9bc28ce59acffbf6e8a28d45a17df7590c6aa - languageName: node - linkType: hard - "hoist-non-react-statics@npm:^2.3.1": version: 2.5.5 resolution: "hoist-non-react-statics@npm:2.5.5" @@ -25063,9 +24446,9 @@ __metadata: linkType: hard "http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 languageName: node linkType: hard @@ -25350,13 +24733,6 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^3.3.5": - version: 3.3.10 - resolution: "ignore@npm:3.3.10" - checksum: 23e8cc776e367b56615ab21b78decf973a35dfca5522b39d9b47643d8168473b0d1f18dd1321a1bab466a12ea11a2411903f3b21644f4d5461ee0711ec8678bd - languageName: node - linkType: hard - "ignore@npm:^5.1.4, ignore@npm:^5.2.0": version: 5.2.0 resolution: "ignore@npm:5.2.0" @@ -25364,15 +24740,6 @@ __metadata: languageName: node linkType: hard -"image-size@npm:^0.7.2": - version: 0.7.5 - resolution: "image-size@npm:0.7.5" - bin: - image-size: bin/image-size.js - checksum: f88860c9d9b5c8ad00d3de9d6f5ba105bda5a5024bfb6b90559a075a4b838ed4f5d3cba14edf0f18fe5d75df596a172b52feca43848e11c34f31f4df2c88a011 - languageName: node - linkType: hard - "immer@npm:9.0.7": version: 9.0.7 resolution: "immer@npm:9.0.7" @@ -25487,13 +24854,6 @@ __metadata: languageName: node linkType: hard -"indexof@npm:0.0.1": - version: 0.0.1 - resolution: "indexof@npm:0.0.1" - checksum: 0fb04e8b147b8585d981a6df1564f25bb3678d6fa74e33e5cecc1464b10f78e15e8ef6bb688f135fe5c2844a128fac8a7831cbe5adc81fdcf12681b093dfcc25 - languageName: node - linkType: hard - "infer-owner@npm:^1.0.4": version: 1.0.4 resolution: "infer-owner@npm:1.0.4" @@ -25940,13 +25300,6 @@ __metadata: languageName: node linkType: hard -"is-function@npm:^1.0.1": - version: 1.0.2 - resolution: "is-function@npm:1.0.2" - checksum: 7d564562e07b4b51359547d3ccc10fb93bb392fd1b8177ae2601ee4982a0ece86d952323fc172a9000743a3971f09689495ab78a1d49a9b14fc97a7e28521dc0 - languageName: node - linkType: hard - "is-generator-fn@npm:^2.0.0": version: 2.1.0 resolution: "is-generator-fn@npm:2.1.0" @@ -26386,13 +25739,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:2.0.1": - version: 2.0.1 - resolution: "isarray@npm:2.0.1" - checksum: a0521973213019b82889f028a41528ce7a3f103a710357b6875066202ee5a1fa9ae4135f915f3be9160b8c7a3ae4b39b073b804da3b58fa6b72f8b4b1e3dfef5 - languageName: node - linkType: hard - "isarray@npm:^1.0.0, isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -27166,20 +26512,6 @@ __metadata: languageName: node linkType: hard -"jimp@npm:^0.10.3": - version: 0.10.3 - resolution: "jimp@npm:0.10.3" - dependencies: - "@babel/runtime": ^7.7.2 - "@jimp/custom": ^0.10.3 - "@jimp/plugins": ^0.10.3 - "@jimp/types": ^0.10.3 - core-js: ^3.4.1 - regenerator-runtime: ^0.13.3 - checksum: ace7d9d76c2802e26392603c1fdfc988e7c468941ac127405f6752db9b5c89e1e27f202f96b764eede001ac245be0b46f52bb87eb91a42374027050568a7e816 - languageName: node - linkType: hard - "jju@npm:~1.4.0": version: 1.4.0 resolution: "jju@npm:1.4.0" @@ -27228,20 +26560,6 @@ __metadata: languageName: node linkType: hard -"jpeg-js@npm:^0.3.4": - version: 0.3.7 - resolution: "jpeg-js@npm:0.3.7" - checksum: 85a1ab09fe696fdd7b94ee077f1b8d1de307ffd7a83c0c45cc5cc56b0d7b47e561b92f6f090165648387875d674ba3076e3fedf395d5be4ef46760b6ebc804fe - languageName: node - linkType: hard - -"js-base64@npm:^2.5.1": - version: 2.6.4 - resolution: "js-base64@npm:2.6.4" - checksum: 5f4084078d6c46f8529741d110df84b14fac3276b903760c21fa8cc8521370d607325dfe1c1a9fbbeaae1ff8e602665aaeef1362427d8fef704f9e3659472ce8 - languageName: node - linkType: hard - "js-base64@npm:^3.6.0": version: 3.7.4 resolution: "js-base64@npm:3.7.4" @@ -27526,13 +26844,6 @@ __metadata: languageName: node linkType: hard -"json-parse-better-errors@npm:^1.0.1": - version: 1.0.2 - resolution: "json-parse-better-errors@npm:1.0.2" - checksum: ff2b5ba2a70e88fd97a3cb28c1840144c5ce8fae9cbeeddba15afa333a5c407cf0e42300cd0a2885dbb055227fe68d405070faad941beeffbfde9cf3b2c78c5d - languageName: node - linkType: hard - "json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" @@ -28410,22 +27721,6 @@ __metadata: languageName: node linkType: hard -"load-bmfont@npm:^1.3.1, load-bmfont@npm:^1.4.0": - version: 1.4.1 - resolution: "load-bmfont@npm:1.4.1" - dependencies: - buffer-equal: 0.0.1 - mime: ^1.3.4 - parse-bmfont-ascii: ^1.0.3 - parse-bmfont-binary: ^1.0.5 - parse-bmfont-xml: ^1.1.4 - phin: ^2.9.1 - xhr: ^2.0.1 - xtend: ^4.0.0 - checksum: 688d932fb0dc4c9333747736ccd926261f0b91734b7bdb6ff24f8659ef068a0f0b2278084b208851afac0beec79af7bd6664fe2ed5b6c5e1db88755fc25f785e - languageName: node - linkType: hard - "load-json-file@npm:^1.0.0": version: 1.1.0 resolution: "load-json-file@npm:1.1.0" @@ -28439,18 +27734,6 @@ __metadata: languageName: node linkType: hard -"load-json-file@npm:^4.0.0": - version: 4.0.0 - resolution: "load-json-file@npm:4.0.0" - dependencies: - graceful-fs: ^4.1.2 - parse-json: ^4.0.0 - pify: ^3.0.0 - strip-bom: ^3.0.0 - checksum: 8f5d6d93ba64a9620445ee9bde4d98b1eac32cf6c8c2d20d44abfa41a6945e7969456ab5f1ca2fb06ee32e206c9769a20eec7002fe290de462e8c884b6b8b356 - languageName: node - linkType: hard - "load-yaml-file@npm:^0.2.0": version: 0.2.0 resolution: "load-yaml-file@npm:0.2.0" @@ -28730,7 +28013,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.13, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.7.0, lodash@npm:~4.17.0, lodash@npm:~4.17.15": +"lodash@npm:4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.7.0, lodash@npm:~4.17.0, lodash@npm:~4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -29399,13 +28682,6 @@ __metadata: languageName: node linkType: hard -"merge@npm:^1.2.1": - version: 1.2.1 - resolution: "merge@npm:1.2.1" - checksum: 2298c4fdcf64561f320b92338681f7ffcafafb579a6e294066ae3e7bd10ae25df363903d2f028072733b9f79a1f75d2b999aef98ad5d73de13641da39cda0913 - languageName: node - linkType: hard - "meros@npm:1.2.1, meros@npm:^1.1.4": version: 1.2.1 resolution: "meros@npm:1.2.1" @@ -29418,6 +28694,13 @@ __metadata: languageName: node linkType: hard +"meta-png@npm:1.0.5": + version: 1.0.5 + resolution: "meta-png@npm:1.0.5" + checksum: b8c92a696d5e78ef7f0ab53eb9039f108ee0c55cdac9d70e92e5fe2187b090176c0489426d58e594c7d1096481dac37913dd1265f28dfa39926db4d702a5e21d + languageName: node + linkType: hard + "methods@npm:^1.0.0, methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" @@ -29807,7 +29090,7 @@ __metadata: languageName: node linkType: hard -"mime@npm:1.6.0, mime@npm:^1.3.4": +"mime@npm:1.6.0": version: 1.6.0 resolution: "mime@npm:1.6.0" bin: @@ -29869,15 +29152,6 @@ __metadata: languageName: node linkType: hard -"min-document@npm:^2.19.0": - version: 2.19.0 - resolution: "min-document@npm:2.19.0" - dependencies: - dom-walk: ^0.1.0 - checksum: da6437562ea2228041542a2384528e74e22d1daa1a4ec439c165abf0b9d8a63e17e3b8a6dc6e0c731845e85301198730426932a0e813d23f932ca668340c9623 - languageName: node - linkType: hard - "min-indent@npm:^1.0.0": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -30093,24 +29367,6 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:0.3.0": - version: 0.3.0 - resolution: "mkdirp@npm:0.3.0" - checksum: 3ec9cda8bd89b64892728e5092bc79e88382e444d4bbde040c2fb8d7034dc70682cfdd729e93241fd5243d2397324c420ef68c717d806db51bf96c0fc80f4b1d - languageName: node - linkType: hard - -"mkdirp@npm:^0.5.1": - version: 0.5.5 - resolution: "mkdirp@npm:0.5.5" - dependencies: - minimist: ^1.2.5 - bin: - mkdirp: bin/cmd.js - checksum: 3bce20ea525f9477befe458ab85284b0b66c8dc3812f94155af07c827175948cdd8114852ac6c6d82009b13c1048c37f6d98743eb019651ee25c39acc8aabe7d - languageName: node - linkType: hard - "mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -30147,6 +29403,15 @@ __metadata: languageName: node linkType: hard +"move-file@npm:2.1.0": + version: 2.1.0 + resolution: "move-file@npm:2.1.0" + dependencies: + path-exists: ^4.0.0 + checksum: 2b92bbe047a302b593fcb2c0bf1131bb090ec80b3264569fc80d782c8ff829eecc13573943fa4d804c9747dec612ef2d1e84a5cfcf29cbc64a69ffcbb7ea09b3 + languageName: node + linkType: hard + "mri@npm:1.1.4": version: 1.1.4 resolution: "mri@npm:1.1.4" @@ -30453,6 +29718,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: latest + checksum: 2508bd2d2981945406243a7bd31362fc7af8b70b8b4d65f869c61731800058fb818cc2fd36c8eac714ddd0e568cc85becf5e165cebbdf7b5024d5151bbc75ea1 + languageName: node + linkType: hard + "node-cache@npm:^5.1.2": version: 5.1.2 resolution: "node-cache@npm:5.1.2" @@ -30648,17 +29922,6 @@ __metadata: languageName: node linkType: hard -"nopt@npm:1.0.10, nopt@npm:~1.0.10": - version: 1.0.10 - resolution: "nopt@npm:1.0.10" - dependencies: - abbrev: 1 - bin: - nopt: ./bin/nopt.js - checksum: f62575aceaa3be43f365bf37a596b89bbac2e796b001b6d2e2a85c2140a4e378ff919e2753ccba959c4fd344776fc88c29b393bc167fa939fb1513f126f4cd45 - languageName: node - linkType: hard - "nopt@npm:^5.0.0": version: 5.0.0 resolution: "nopt@npm:5.0.0" @@ -30681,6 +29944,17 @@ __metadata: languageName: node linkType: hard +"nopt@npm:~1.0.10": + version: 1.0.10 + resolution: "nopt@npm:1.0.10" + dependencies: + abbrev: 1 + bin: + nopt: ./bin/nopt.js + checksum: f62575aceaa3be43f365bf37a596b89bbac2e796b001b6d2e2a85c2140a4e378ff919e2753ccba959c4fd344776fc88c29b393bc167fa939fb1513f126f4cd45 + languageName: node + linkType: hard + "normalize-package-data@npm:^2.3.2, normalize-package-data@npm:^2.5.0": version: 2.5.0 resolution: "normalize-package-data@npm:2.5.0" @@ -30847,7 +30121,7 @@ __metadata: languageName: node linkType: hard -"npmlog@npm:^4.0.1, npmlog@npm:^4.1.2": +"npmlog@npm:^4.1.2": version: 4.1.2 resolution: "npmlog@npm:4.1.2" dependencies: @@ -31084,13 +30358,6 @@ __metadata: languageName: node linkType: hard -"omggif@npm:^1.0.9": - version: 1.0.10 - resolution: "omggif@npm:1.0.10" - checksum: 15102e46b6fa0fba32d7e948f702623cdc3cdcdfd64b2d33c6e29a61f366ffd0f250da55d66f5217dce5b93ba9c67763fa998652791a5c7f2201a3bde2c4db45 - languageName: node - linkType: hard - "on-finished@npm:2.4.1, on-finished@npm:^2.3.0": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -31473,7 +30740,7 @@ __metadata: languageName: node linkType: hard -"pako@npm:^1.0.10, pako@npm:^1.0.5, pako@npm:~1.0.5": +"pako@npm:^1.0.10, pako@npm:~1.0.5": version: 1.0.11 resolution: "pako@npm:1.0.11" checksum: 1be2bfa1f807608c7538afa15d6f25baa523c30ec870a3228a89579e474a4d992f4293859524e46d5d87fd30fa17c5edf34dbef0671251d9749820b488660b16 @@ -31520,30 +30787,6 @@ __metadata: languageName: node linkType: hard -"parse-bmfont-ascii@npm:^1.0.3": - version: 1.0.6 - resolution: "parse-bmfont-ascii@npm:1.0.6" - checksum: de3f6671f183c3e9d64bb4812b0407693b5fd0d24e9d16b2e106bb9eef809d64a6cc061f39ca29bb10c5c2e47e241e91b7aeefa587391fff7ccb27ab9db5012e - languageName: node - linkType: hard - -"parse-bmfont-binary@npm:^1.0.5": - version: 1.0.6 - resolution: "parse-bmfont-binary@npm:1.0.6" - checksum: ca37fb1e92f5941fddc5342b45857fafd27f00d2bd5fa44dd504bec6faeab97536c95ad45260c2dd5fc4c63de71e525663d3cdac09d038cbca803d97c669add5 - languageName: node - linkType: hard - -"parse-bmfont-xml@npm:^1.1.4": - version: 1.1.4 - resolution: "parse-bmfont-xml@npm:1.1.4" - dependencies: - xml-parse-from-string: ^1.0.0 - xml2js: ^0.4.5 - checksum: 879e5435be44f22b8c4934e2e1d2754a6d90a9ddb16309360daff965e1428d877b673f3d1fafaab4fef437c912a0db9f85545e0dd375ec62df7d4d328450d257 - languageName: node - linkType: hard - "parse-conflict-json@npm:^2.0.1": version: 2.0.1 resolution: "parse-conflict-json@npm:2.0.1" @@ -31594,13 +30837,6 @@ __metadata: languageName: node linkType: hard -"parse-headers@npm:^2.0.0": - version: 2.0.4 - resolution: "parse-headers@npm:2.0.4" - checksum: 29519ac013e100c11a67d0fc64eb33ae86523abf547f71dba36d484dcd16a2835dd11f31303f4ded27c40133dca5a5fe4d15b77f49091e470a6f74a023c59c4a - languageName: node - linkType: hard - "parse-json@npm:^2.2.0": version: 2.2.0 resolution: "parse-json@npm:2.2.0" @@ -31610,16 +30846,6 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^4.0.0": - version: 4.0.0 - resolution: "parse-json@npm:4.0.0" - dependencies: - error-ex: ^1.3.1 - json-parse-better-errors: ^1.0.1 - checksum: 0fe227d410a61090c247e34fa210552b834613c006c2c64d9a05cfe9e89cf8b4246d1246b1a99524b53b313e9ac024438d0680f67e33eaed7e6f38db64cfe7b5 - languageName: node - linkType: hard - "parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -31632,13 +30858,6 @@ __metadata: languageName: node linkType: hard -"parse-package-name@npm:^0.1.0": - version: 0.1.0 - resolution: "parse-package-name@npm:0.1.0" - checksum: 7d69b1deeaf82d5a59536c5a8772c8db003b1415dd2465073343dfab5caf90917ecfffdd71d1c004f5cf963c3881ccb8899d0ff820eac705a272e817e246de14 - languageName: node - linkType: hard - "parse-path@npm:^7.0.0": version: 7.0.0 resolution: "parse-path@npm:7.0.0" @@ -31673,20 +30892,6 @@ __metadata: languageName: node linkType: hard -"parseqs@npm:0.0.6": - version: 0.0.6 - resolution: "parseqs@npm:0.0.6" - checksum: 7fc4ff4ba59764060bb8529875f6d4313056ea6939ff579b22dd7bd6f6033035e1fd2d6a559ab48ef0a7fa29a9d7731c982bfd1594e9115141fe1c328485ce9e - languageName: node - linkType: hard - -"parseuri@npm:0.0.6": - version: 0.0.6 - resolution: "parseuri@npm:0.0.6" - checksum: fa430e40f0c75293a28e5f1023da5f51a5038d5e34c48c517b0d5187143f6bcc67d3091a062b68765db4a22757e488c7d15854f9d1921f2c2b9afa5ca0629a84 - languageName: node - linkType: hard - "parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" @@ -31925,7 +31130,7 @@ __metadata: languageName: node linkType: hard -"path-is-inside@npm:1.0.2, path-is-inside@npm:^1.0.2": +"path-is-inside@npm:1.0.2": version: 1.0.2 resolution: "path-is-inside@npm:1.0.2" checksum: 0b5b6c92d3018b82afb1f74fe6de6338c4c654de4a96123cb343f2b747d5606590ac0c890f956ed38220a4ab59baddfd7b713d78a62d240b20b14ab801fa02cb @@ -32024,15 +31229,6 @@ __metadata: languageName: node linkType: hard -"path-type@npm:^3.0.0": - version: 3.0.0 - resolution: "path-type@npm:3.0.0" - dependencies: - pify: ^3.0.0 - checksum: 735b35e256bad181f38fa021033b1c33cfbe62ead42bb2222b56c210e42938eecb272ae1949f3b6db4ac39597a61b44edd8384623ec4d79bfdc9a9c0f12537a6 - languageName: node - linkType: hard - "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -32177,13 +31373,6 @@ __metadata: languageName: node linkType: hard -"phin@npm:^2.9.1": - version: 2.9.3 - resolution: "phin@npm:2.9.3" - checksum: 7e2abd7be74a54eb7be92dccb1d7a019725c8adaa79ac22a38f25220f9a859393e654ea753a559d326aed7bbc966fadac88270cc8c39d78896f7784219560c47 - languageName: node - linkType: hard - "picocolors@npm:^1.0.0": version: 1.0.0 resolution: "picocolors@npm:1.0.0" @@ -32214,13 +31403,6 @@ __metadata: languageName: node linkType: hard -"pify@npm:^3.0.0": - version: 3.0.0 - resolution: "pify@npm:3.0.0" - checksum: 6cdcbc3567d5c412450c53261a3f10991665d660961e06605decf4544a61a97a54fefe70a68d5c37080ff9d6f4cf51444c90198d1ba9f9309a6c0d6e9f5c4fde - languageName: node - linkType: hard - "pify@npm:^4.0.1": version: 4.0.1 resolution: "pify@npm:4.0.1" @@ -32300,14 +31482,14 @@ __metadata: languageName: node linkType: hard -"pixelmatch@npm:^4.0.2": - version: 4.0.2 - resolution: "pixelmatch@npm:4.0.2" +"pixelmatch@npm:5.3.0": + version: 5.3.0 + resolution: "pixelmatch@npm:5.3.0" dependencies: - pngjs: ^3.0.0 + pngjs: ^6.0.0 bin: pixelmatch: bin/pixelmatch - checksum: 9c5c1329001938cae6d01e2bb84a909ba767f8256bcafc075422cea2a4dbaa8bebd44fceaa4b4ce7cdc36d11f20d4f1ba0cf669851d5649b32d8d1d27e4f5a36 + checksum: f542713d89536551181ad9ddb666a1792ba00a8632d831093232a075cb3ccac05856e7a453ed7d0a41aaef64dcb5962e8ae5cbe646dd2761790d8ee51b0a0743 languageName: node linkType: hard @@ -32359,10 +31541,10 @@ __metadata: languageName: node linkType: hard -"pngjs@npm:^3.0.0, pngjs@npm:^3.3.3": - version: 3.4.0 - resolution: "pngjs@npm:3.4.0" - checksum: 8bd40bd698abd16b72c97b85cb858c80894fbedc76277ce72a784aa441e14795d45d9856e97333ca469b34b67528860ffc8a7317ca6beea349b645366df00bcd +"pngjs@npm:6.0.0, pngjs@npm:^6.0.0": + version: 6.0.0 + resolution: "pngjs@npm:6.0.0" + checksum: ab6c285086060087097eab9fe6b5a528a24f9e79c03dea2b4fd6264ed4fdb5beff4a3257eeeaf2a9dc18249b539609c2a4e4013c567164a1f6b5ba2c974d5ecb languageName: node linkType: hard @@ -32829,9 +32011,9 @@ __metadata: languageName: node linkType: hard -"prebuild-install@npm:^7.1.0": - version: 7.1.0 - resolution: "prebuild-install@npm:7.1.0" +"prebuild-install@npm:^7.1.0, prebuild-install@npm:^7.1.1": + version: 7.1.1 + resolution: "prebuild-install@npm:7.1.1" dependencies: detect-libc: ^2.0.0 expand-template: ^2.0.3 @@ -32840,7 +32022,6 @@ __metadata: mkdirp-classic: ^0.5.3 napi-build-utils: ^1.0.1 node-abi: ^3.3.0 - npmlog: ^4.0.1 pump: ^3.0.0 rc: ^1.2.7 simple-get: ^4.0.0 @@ -32848,7 +32029,7 @@ __metadata: tunnel-agent: ^0.6.0 bin: prebuild-install: bin.js - checksum: 204f2d89c6d6179fa1039036514aa72f7d0b537e421ef72c40840286e318f41489f00f22c6acc725cce6e10d43825b69dcabeaadfc917db781c58cd56fc25f90 + checksum: dbf96d0146b6b5827fc8f67f72074d2e19c69628b9a7a0a17d0fad1bf37e9f06922896972e074197fc00a52eae912993e6ef5a0d471652f561df5cb516f3f467 languageName: node linkType: hard @@ -32885,21 +32066,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^1.16.4": - version: 1.19.1 - resolution: "prettier@npm:1.19.1" - bin: - prettier: ./bin-prettier.js - checksum: bc78219e0f8173a808f4c6c8e0a137dd8ebd4fbe013e63fe1a37a82b48612f17b8ae8e18a992adf802ee2cf7428f14f084e7c2846ca5759cf4013c6e54810e1f - languageName: node - linkType: hard - "prettier@npm:^2.2.1, prettier@npm:^2.7.1": - version: 2.8.1 - resolution: "prettier@npm:2.8.1" + version: 2.8.2 + resolution: "prettier@npm:2.8.2" bin: prettier: bin-prettier.js - checksum: 4f21a0f1269f76fb36f54e9a8a1ea4c11e27478958bf860661fb4b6d7ac69aac1581f8724fa98ea3585e56d42a2ea317a17ff6e3324f40cb11ff9e20b73785cc + checksum: 740c56c2128d587d656ea1dde9bc9c3503dfc94db4f3ac387259215eeb2e216680bdad9d18a0c9feecc6b42cfa188d6fa777df4c36c1d00cedd4199074fbfbd2 languageName: node linkType: hard @@ -33695,15 +32867,6 @@ __metadata: languageName: node linkType: hard -"react-icons@npm:^4.4.0": - version: 4.4.0 - resolution: "react-icons@npm:4.4.0" - peerDependencies: - react: "*" - checksum: dd93a1dcc8ac8a3cf46a2b33e80b586fa967331fa5fcb90d061abf276155a8363a331643e203d67cb9bab4261d00a757da854bcce1fce2c6e4258bf3e33f711b - languageName: node - linkType: hard - "react-immutable-proptypes@npm:2.2.0": version: 2.2.0 resolution: "react-immutable-proptypes@npm:2.2.0" @@ -34396,7 +33559,7 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.13.11, regenerator-runtime@npm:^0.13.3, regenerator-runtime@npm:^0.13.4": +"regenerator-runtime@npm:^0.13.11, regenerator-runtime@npm:^0.13.4": version: 0.13.11 resolution: "regenerator-runtime@npm:0.13.11" checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4 @@ -35234,15 +34397,6 @@ __metadata: languageName: node linkType: hard -"sanitize-filename@npm:^1.6.1": - version: 1.6.3 - resolution: "sanitize-filename@npm:1.6.3" - dependencies: - truncate-utf8-bytes: ^1.0.0 - checksum: aa733c012b7823cf65730603cf3b503c641cee6b239771d3164ca482f22d81a50e434a713938d994071db18e4202625669cc56bccc9d13d818b4c983b5f47fde - languageName: node - linkType: hard - "sax@npm:1.2.1": version: 1.2.1 resolution: "sax@npm:1.2.1" @@ -35621,6 +34775,23 @@ __metadata: languageName: node linkType: hard +"sharp@npm:0.31.2": + version: 0.31.2 + resolution: "sharp@npm:0.31.2" + dependencies: + color: ^4.2.3 + detect-libc: ^2.0.1 + node-addon-api: ^5.0.0 + node-gyp: latest + prebuild-install: ^7.1.1 + semver: ^7.3.8 + simple-get: ^4.0.1 + tar-fs: ^2.1.1 + tunnel-agent: ^0.6.0 + checksum: 076717b7a073ea47bb522ff2931b74b6608daeb6f7ae334e4848d47fdf4d23bcb18cd49044fd5fb27ef27a1a4aa87d141894d67d1c4bb15a6e2e63cf4dbe329e + languageName: node + linkType: hard + "shebang-command@npm:^1.2.0": version: 1.2.0 resolution: "shebang-command@npm:1.2.0" @@ -35778,13 +34949,6 @@ __metadata: languageName: node linkType: hard -"slash@npm:^1.0.0": - version: 1.0.0 - resolution: "slash@npm:1.0.0" - checksum: 4b6e21b1fba6184a7e2efb1dd173f692d8a845584c1bbf9dc818ff86f5a52fc91b413008223d17cc684604ee8bb9263a420b1182027ad9762e35388434918860 - languageName: node - linkType: hard - "slash@npm:^2.0.0": version: 2.0.0 resolution: "slash@npm:2.0.0" @@ -35864,68 +35028,6 @@ __metadata: languageName: node linkType: hard -"socket.io-adapter@npm:~1.1.0": - version: 1.1.2 - resolution: "socket.io-adapter@npm:1.1.2" - checksum: 8e18df7f8c471001b65e43542c5c743b63f041781bca097b4a407dd199dd757c14c0d470992f84923f48e75729a2ac0ae634fec820f20881cadae8a495bf68e9 - languageName: node - linkType: hard - -"socket.io-client@npm:2.5.0, socket.io-client@npm:^2.2.0": - version: 2.5.0 - resolution: "socket.io-client@npm:2.5.0" - dependencies: - backo2: 1.0.2 - component-bind: 1.0.0 - component-emitter: ~1.3.0 - debug: ~3.1.0 - engine.io-client: ~3.5.0 - has-binary2: ~1.0.2 - indexof: 0.0.1 - parseqs: 0.0.6 - parseuri: 0.0.6 - socket.io-parser: ~3.3.0 - to-array: 0.1.4 - checksum: a33accaf02c4a4c47ac9b09018fa4b9fa67a648a0cc92602e520ec884520050796786eb0df78fc1a9bf51e7b3d7b50ba4ffd3bda6e44df2c253a77100124669c - languageName: node - linkType: hard - -"socket.io-parser@npm:~3.3.0": - version: 3.3.3 - resolution: "socket.io-parser@npm:3.3.3" - dependencies: - component-emitter: ~1.3.0 - debug: ~3.1.0 - isarray: 2.0.1 - checksum: 6cf464e324e4207811dbf994822c0aa18ba01d615fbed8e3f069927e0e402fdc9b3ac3d2657f18d481fb8c584b1fd40152ced15b3686982a51ec843c6a76dbc2 - languageName: node - linkType: hard - -"socket.io-parser@npm:~3.4.0": - version: 3.4.1 - resolution: "socket.io-parser@npm:3.4.1" - dependencies: - component-emitter: 1.2.1 - debug: ~4.1.0 - isarray: 2.0.1 - checksum: f8bac61298375680aceb6c72ffbb47a2d950daa05e01a3b67d9330945073ae01e35aa131dcddd7e0b354ca302c51742bebaae57a50c188d4c1fe8af9461aa38d - languageName: node - linkType: hard - -"socket.io@npm:^2.2.0": - version: 2.5.0 - resolution: "socket.io@npm:2.5.0" - dependencies: - debug: ~4.1.0 - engine.io: ~3.6.0 - has-binary2: ~1.0.2 - socket.io-adapter: ~1.1.0 - socket.io-client: 2.5.0 - socket.io-parser: ~3.4.0 - checksum: 6f20a413352cb0a8bb13063249fe425c81fb54beca1bdd71221262305cf21232e6a37fdd4a232b5695dbd4d3204281a9313829c9c373c5ba01c5bd7218edd094 - languageName: node - linkType: hard - "sockjs@npm:^0.3.24": version: 0.3.24 resolution: "sockjs@npm:0.3.24" @@ -36003,7 +35105,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:^0.5.10, source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20": +"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.21, source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -37382,13 +36484,6 @@ __metadata: languageName: node linkType: hard -"timm@npm:^1.6.1": - version: 1.7.1 - resolution: "timm@npm:1.7.1" - checksum: c80df538ec7fae50a0e3183931b20fbe97f6f2c06907d9675eb7b9d90b3f788af7742285c730192db3b066c4ab22ebae75f8d21970c5b03f38d928d5bb2a0339 - languageName: node - linkType: hard - "timsort@npm:^0.3.0": version: 0.3.0 resolution: "timsort@npm:0.3.0" @@ -37424,13 +36519,6 @@ __metadata: languageName: node linkType: hard -"tinycolor2@npm:^1.4.1": - version: 1.4.2 - resolution: "tinycolor2@npm:1.4.2" - checksum: 57ed262e08815a4ab0ed933edafdbc6555a17081781766149813b44a080ecbe58b3ee281e81c0e75b42e4d41679f138cfa98eabf043f829e0683c04adb12c031 - languageName: node - linkType: hard - "title-case@npm:^3.0.3": version: 3.0.3 resolution: "title-case@npm:3.0.3" @@ -37474,13 +36562,6 @@ __metadata: languageName: node linkType: hard -"to-array@npm:0.1.4": - version: 0.1.4 - resolution: "to-array@npm:0.1.4" - checksum: 396a04df5a5e74df5c8891ebb819d575d626e2929312fec3d649b4d38203bbf7185c270cb31fcad903564743895e935f2e965770bd1c6eb137dde48c4d60788d - languageName: node - linkType: hard - "to-arraybuffer@npm:^1.0.0": version: 1.0.1 resolution: "to-arraybuffer@npm:1.0.1" @@ -37654,15 +36735,6 @@ __metadata: languageName: node linkType: hard -"truncate-utf8-bytes@npm:^1.0.0": - version: 1.0.2 - resolution: "truncate-utf8-bytes@npm:1.0.2" - dependencies: - utf8-byte-length: ^1.0.1 - checksum: ad097314709ea98444ad9c80c03aac8da805b894f37ceb5685c49ad297483afe3a5ec9572ebcaff699dda72b6cd447a2ba2a3fd10e96c2628cd16d94abeb328a - languageName: node - linkType: hard - "tryer@npm:^1.0.1": version: 1.0.1 resolution: "tryer@npm:1.0.1" @@ -38126,15 +37198,6 @@ __metadata: languageName: node linkType: hard -"unidiff@npm:1.0.2": - version: 1.0.2 - resolution: "unidiff@npm:1.0.2" - dependencies: - diff: ^2.2.2 - checksum: 442d699d44a07f7511e2ec5b6433fb562d6ddce9eb91c6c28ff23bdda60595d9d7e8c6341937f18996ca03c198d93cc9b8fb448dde211ddeeb45bb45bff8ca1e - languageName: node - linkType: hard - "unified@npm:^10.0.0": version: 10.1.0 resolution: "unified@npm:10.1.0" @@ -38442,22 +37505,6 @@ __metadata: languageName: node linkType: hard -"utf8-byte-length@npm:^1.0.1": - version: 1.0.4 - resolution: "utf8-byte-length@npm:1.0.4" - checksum: f188ca076ec094d58e7009fcc32623c5830c7f0f3e15802bfa4fdd1e759454a481fc4ac05e0fa83b7736e77af628a9ee0e57dcc89683d688fde3811473e42143 - languageName: node - linkType: hard - -"utif@npm:^2.0.1": - version: 2.0.1 - resolution: "utif@npm:2.0.1" - dependencies: - pako: ^1.0.5 - checksum: 66b0bffc18f08834a34c44846f189b2223418e8bddfff3882e07fccea54436737f7334e27c86cbff46e247487dc92377b7a17428a7aafc5d5c733d61bed39038 - languageName: node - linkType: hard - "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -39116,7 +38163,7 @@ __metadata: languageName: node linkType: hard -"whatwg-fetch@npm:>=0.10.0, whatwg-fetch@npm:^3.0.0, whatwg-fetch@npm:^3.4.1": +"whatwg-fetch@npm:>=0.10.0, whatwg-fetch@npm:^3.4.1": version: 3.6.2 resolution: "whatwg-fetch@npm:3.6.2" checksum: ee976b7249e7791edb0d0a62cd806b29006ad7ec3a3d89145921ad8c00a3a67e4be8f3fb3ec6bc7b58498724fd568d11aeeeea1f7827e7e1e5eae6c8a275afed @@ -39435,21 +38482,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:~7.4.2": - version: 7.4.6 - resolution: "ws@npm:7.4.6" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 3a990b32ed08c72070d5e8913e14dfcd831919205be52a3ff0b4cdd998c8d554f167c9df3841605cde8b11d607768cacab3e823c58c96a5c08c987e093eb767a - languageName: node - linkType: hard - "xcase@npm:^2.0.1": version: 2.0.1 resolution: "xcase@npm:2.0.1" @@ -39457,18 +38489,6 @@ __metadata: languageName: node linkType: hard -"xhr@npm:^2.0.1": - version: 2.6.0 - resolution: "xhr@npm:2.6.0" - dependencies: - global: ~4.4.0 - is-function: ^1.0.1 - parse-headers: ^2.0.0 - xtend: ^4.0.0 - checksum: a1db277e37737caf3ed363d2a33ce4b4ea5b5fc190b663a6f70bc252799185b840ccaa166eaeeea4841c9c60b87741f0a24e29cbcf6708dd425986d4df186d2f - languageName: node - linkType: hard - "xml-but-prettier@npm:^1.0.1": version: 1.0.1 resolution: "xml-but-prettier@npm:1.0.1" @@ -39513,13 +38533,6 @@ __metadata: languageName: node linkType: hard -"xml-parse-from-string@npm:^1.0.0": - version: 1.0.1 - resolution: "xml-parse-from-string@npm:1.0.1" - checksum: 5155cb98e428409829f4060ce542c55438b2f7646d11fd306d850eaf12d35c06ffd9e86d76aa5230121a533b958fd1a319d6f90a5c113391853d0ff01f4da7bb - languageName: node - linkType: hard - "xml2js@npm:0.4.19": version: 0.4.19 resolution: "xml2js@npm:0.4.19" @@ -39530,7 +38543,7 @@ __metadata: languageName: node linkType: hard -"xml2js@npm:^0.4.11, xml2js@npm:^0.4.19, xml2js@npm:^0.4.23, xml2js@npm:^0.4.5": +"xml2js@npm:^0.4.11, xml2js@npm:^0.4.19, xml2js@npm:^0.4.23": version: 0.4.23 resolution: "xml2js@npm:0.4.23" dependencies: @@ -39582,13 +38595,6 @@ __metadata: languageName: node linkType: hard -"xmlhttprequest-ssl@npm:~1.6.2": - version: 1.6.3 - resolution: "xmlhttprequest-ssl@npm:1.6.3" - checksum: ac8e5de1cdd170bddb928de75393e8977e7eb80c0d8c24fe4be07f6aa1d5c8e2e42296d29abca6591ec2046cc708c220791ecfa56db43c958b8e4de8e7d39984 - languageName: node - linkType: hard - "xpath@npm:0.0.32": version: 0.0.32 resolution: "xpath@npm:0.0.32" @@ -39793,13 +38799,6 @@ __metadata: languageName: node linkType: hard -"yeast@npm:0.1.2": - version: 0.1.2 - resolution: "yeast@npm:0.1.2" - checksum: 81a250b69f601fed541e9518eb2972e75631dd81231689503d7f288612d4eec793b29c208d6807fd6bfc4c2a43614d0c6db233739a4ae6223e244aaed6a885c0 - languageName: node - linkType: hard - "yeoman-environment@npm:^3.9.1": version: 3.13.0 resolution: "yeoman-environment@npm:3.13.0"